Possible simplifications for the tutorial contract (Fungible Tokens)

As a first time reader, it seemed a little overwhelming when going through tutorial 3 ā€œFungible Tokensā€. Thereā€™s a ton of public interfaces, but are they indispensable? Simply using the Vault type in the type casts while interacting with storage or capabilities seems sufficient.

Hereā€™s the base contract I composed, without any interfaces.

pub contract WildeToken {
  pub var tokenSupply: UFix64;

  pub resource Vault {
    pub var balance: UFix64;

    init(balance: UFix64) {
      self.balance = balance;
    }

    pub fun withdraw(amount: UFix64): @Vault {
      self.balance = self.balance - amount;
      return <- create Vault(balance: amount);
    }
    pub fun deposit(from: @Vault) {
      self.balance = self.balance + from.balance;
      destroy from;
    }
  }

  init() {
    self.tokenSupply = 1000.00;

    let genesisVault <- create Vault(balance: self.tokenSupply);
    self.account.save(<- genesisVault, to: /storage/WildeVault);
  }

  pub fun createVault(): @Vault {
    return <- create Vault(balance: 0.0);
  }
}

As far as I can tell, every example/idea that flows in the tutorial when it comes to fungible tokens still holds. I would really appreciate comments if Iā€™m thinking about this wrong, thanks!

Hi @morgan, Iā€™m only learning about Cadende/flow for a few weeks too, so what I say might not be completely correct, but let me just give me 2 cents here.

Definitely a valid question of yours.
I believe that the contract that you wrote would perfectly work.

However, I believe the interfaces are incredibly important for security reasons.
Imagine that your contract would be deployed.
And 0x02 has a vault with this WildeToken and wants to make a public capability so that people can deposit more WildeTokens in his vault. (of course, he does not want other people to be able to withdraw from his vault) In that case, he would use the following operation in a transaction (with acct is the authAccount of 0x02):

acct.link<&WildeToken.Vault{WildeToken.Receiver, WildeToken.Balance}>(/public/MainReceiver, target: /storage/MainVault)

But since there are no public interfaces for WildeToken, that code would be:

acct.link<&WildeToken.Vault>(/public/MainReceiver, target: /storage/MainVault)

Now, this second code example is extremely dangerous. By not restricting the access (with the interfaces), someone who ā€˜borrowsā€™ that capability can perform all existing public actions on that vault of 0x02. He can even perform a Withdrawal and empty the vault.

In short: Capabilities are used to give access to certain resources, while interfaces are used for restricting the actions that can be performed on/with the resource. Thatā€™s why I think you should always have both.

Although this security aspect might be a bit complicated for a tutorial, I do not think it is a good idea to remove it.

I hope I made myself clear. Please let me know if my explanation makes sense :slight_smile:

1 Like

Thatā€™s awesome @jerre ! Thanks for the insight. I havenā€™t tested the contract in such a way and it seemed to perform exactly as the one in the tutorial.

I now believe that it would be best to start with a contract like the one I described and the showcase a transaction thatā€™s able to withdraw funds without the ownerā€™s consent and THEN introducing interfaces as a fix to a problem after the demonstration.

Thanks @morgan :slight_smile:
I just tested it in the playground and my reasoning seems to be valid. This is what I did:

  1. Deployed WildeToken contract on 0x01
  2. Linked a capability to the vault of 0x01
acct.link<&WildeToken.Vault>(/public/MainReceiver, target: /storage/WildeVault)
  1. Initiated 0x02 by creating a new, empty vault and storing it
  2. Steal the coins from 0x01 with the following transaction:
// Steal Tokens

import WildeToken from 0x01

transaction {

var myVaultRef: &WildeToken.Vault

  prepare(acct: AuthAccount) {
    self.myVaultRef = acct.borrow<&WildeToken.Vault>(from: /storage/MainVault)
        ?? panic("Could not borrow a reference to the owner's vault")
  }

  execute {
    let victim = getAccount(0x01)

    let victimRef = victim.getCapability(/public/MainReceiver)
                      .borrow<&WildeToken.Vault>()
                      ?? panic("Could not borrow a reference to the receiver")

    var coins <- victimRef.withdraw(amount: 100.0)

    self.myVaultRef.deposit(from: <- coins)

    log("Transfer succeeded!")
  }
}

Iā€™m with you! On an educational level, it would indeed be very interesting to have it explained in such way as the you put stress on the security breach.

On the other hand, I could also argue that you want all examples in the playground to be perfect and complete. People might end up copying the code from the playground without really understanding it, which can lead to less secure contracts (instead of more secure contracts)

1 Like

Very cool stuff @jerre ! One thing that wasnā€™t mentioned, although I guess itā€™s implied, which account signed the transaction (I understand itā€™s 0x02, but could well be any account)?

@morgan Thanks man!
In this case it was 0x02. But it could indeed be any account that is able to receive the tokens (= has a WildeToken vault in storage)
And as anyone can create such a vault, anyone can steal.
Note that 0x01 is also able to steal them back from 0x02.

Haha, so a wild(e) west token ā€“ anything goes :smiley: I wonder how a society would operate with such a currency where stealing is easyā€¦

Haha indeed, in such a world, possessions wouldnā€™t mean anything so actually, there is no point in stealing :thinking:

In the example above, it is also possible to stop the ā€˜stealingā€™. If 0x02 would not create any public capability, nobody (except for himself) would be able to reach his vault or perform any actions with it.
But that would also mean that nobody can send him WildeTokens :wink: