Creating a token on the Flow blockchain? Don’t forget this VITAL part!
Disclaimer: Do not be alarmed by the title and thumbnail. Nothing has gone wrong, I just want people who are writing smart contracts to make sure they understand this very important part of token smart contracts.
EDIT Oct 2024: The Cadence code in the article uses Cadence 0.42 which is incompatible with the current version of Cadence on all Flow networks. The themes and advice are all mostly still the same though but the code examples will need to be updated to the latest Cadence version.
Welcome to my Cadence blog! I’m Josh, a smart contract developer on the Flow team at Dapper Labs, and I am here today to tell you about a critical piece of all token Cadence smart contracts that everyone needs to be aware of, at least until this part of the functionality of a token is built into the language.
If you’re new here, I would recommend that you start out by reading my introductory blog post about getting started with Flow and Cadence:
What is a token smart contract?
The word “token” here can refer to either of these two types of smart contracts:
- Fungible Token (FT): Similar to a regular currency, where every token is exactly the same as every other token and has the same value. ERC-20 is the fungible token standard on Ethereum, and
FungibleToken
is the comparable standard on the Flow blockchain. The FLOW network cryptocurrency is a fungible token that implements the FlowFungibleToken
standard. - Non-Fungible Token (NFT): These have been ALL THE RAGE recently. NFTs are assets that are all grouped under a connected theme, but each token is unique and has different metadata and values. ERC-721 and ERC-1155 are the NFT standards on Ethereum, and
NonFungibleToken
is the comparable standard on the Flow blockchain. NBA Top Shot Moments are examples of Flow NFTs
These two types of smart contracts are important because there are the most common types of smart contracts on most blockchains. These contracts need to be interoperable with each other, so they need to utilize a common standard. We use a Cadence contract interface to define a standard.
Interfaces
Interfaces are a VERY important part of Cadence programming, so if you haven’t read the language reference document for interfaces, please click on the link above and get started! 😃
Any contract can implement the functionality defined in a contract interface. For tokens, these interfaces contain fields like balance
and functions like withdraw
and deposit
. They enforce that every smart contract that conforms to the interface has to follow all the same rules.
Then, other contracts can specify that they can interact with any token that follows the common interface. This makes interoperability WAY easier. Anyone can plug a new token into the ecosystem or a new app that uses the common token without having to do any specialized integration.
You can see the standards for FungibleToken
here:
And the standards for NonFungibleToken
here:
Interfaces (for now) specify EXACT signatures and types
Take a look at the fungible token interface. Every time the @Vault
type is specified, such as in the deposit function or the withdraw function, @Vault
refers to @FungibleToken.Vault
, which effectively means that the parameter or return value is the FungibleToken.Vault
type.
Contracts that implement the interface have to match the interface specifications exactly. The exact function signature for deposit
has to be
pub fun deposit(from: @FungibleToken.Vault)
So if I was making a JoshToken
contract, my deposit
function would not be allowed to accept a @JoshToken.Vault
. It would still have to accept a FungibleToken.Vault
, just like the standard interface specifies. If someone wanted to call my deposit
function with a JoshToken.Vault
, they would first have to cast it to a FungibleToken.Vault
before passing it as a parameter.
This also applies to the NFT standard. The deposit function in implementations of the standards HAS to be
pub fun deposit(token: @NonFungibleToken.NFT)
This causes a problem! If the type of the parameter that is passed to the deposit function is just a generic token, then anyone could deposit any token they want into your Vault
or Collection
! This is obviously be a problem, because supply of the token could be artificially increased by depositing random tokens into any Vault
.
The CRITICAL PART
This leads me to the subject of the title of this post: The critical part of every token contract that you cannot forget. In order to ensure that only tokens of the correct type are deposited to your Vault
or Collection
, you need to cast the generic object to the concrete type of your contract with a line like this.
// force-cast `from` to a concrete ExampleToken.Vault
let vault <- from as! @ExampleToken.Vault
as!
is the force-cast operator. It attempts to cast the object to the left of the operator as the type to the right of the operator. If the cast succeeds, meaning that the object is the type on the right, the execution of the function continues with the object as the new casted type. If the cast fails, then execution halts and the state is reverted. With this line, if a different token type is deposited, then the deposit will fail and you can maintain the integrity of your token!
You should see this line in many widely used smart contracts on mainnet, such as the FlowToken contract and the NBA Top Shot contract. If you find a token contract that doesn’t have this line in its deposit function, you should try to update it immediately if you can or contact the developer if you cant.
If you are copying and pasting popular smart contracts like FlowToken or Top Shot, this will already be included, but it is still important to ensure it is there.
Why can’t the type system handle this?
You might be thinking, “why does this important behavior need to be enforced by the implementation and not the type system?”
The answer is that this behavior can be enforced by the type system, but is complicated. The Cadence team has unfortunately not been able to add in this feature yet, but plans to include it in the future. We will also be having a token standard retrospective with the community in the future to discuss how the token standards could be improved. If you have thoughts, please let me know here or in the Flow discord and we can make sure you are included in the conversation!
This also underscores an important thing to remember if you are a Cadence developer: Cadence is still a very young language, and is still evolving very quickly. We’re constantly adding significant new features, and figuring out best practices for a wide variety of applications! If you are a Cadence developer right now, you are an early adopter and are part of the beginning of the first mainstream resource-oriented programming language! We are on the cutting edge of this technology and are learning new things every day, and every member of the community is extremely valuable in making Cadence as good as it can be.
I know dealing with things like the subject of this post can be a bit awkward sometimes, and it can be frustrating to work with a language that is still a work in progress, but that is what comes with being on the cutting edge. You should be proud that you are one of the first to take the plunge!
See ya next time, all-star! ⛹️♀️
Flow Discord: https://discord.gg/flow
Flow Forum: https://forum.onflow.org
Flow Github: https://github.com/onflow/flow