Throughout this article series, we’ve its , and some of the most , all while comparing and contrasting to Ethereum. introduced you to the Flow blockchain, smart contract language Cadence essential tools developers should know In this article, we will talk about best practices and patterns that should be followed when using the Cadence language and developing solutions on the Flow network, as well as patterns to avoid. Best practices Use Flow Test Network and Flow Emulator One of the most important steps in developing on any blockchain is testing your projects in a simulated environment. Flow blockchain is no different. For this purpose, the and are both crucial components to include in your development process. Not only do they allow you to understand how your dapp will perform, but they will also help you catch any critical bugs before deploying to mainnet. Flow testnet Flow Emulator Use Standard Contracts Two of the most common use cases for any blockchain are digital currencies and providing proof of digital ownership through NFTs. Instead of trying to rewrite the necessary contracts from scratch, Flow developers should always import the official core contracts to ensure user safety and consistency with the rest of the network. There are currently three standard contracts you should use: — Used to create new tokens Fungible Token — Used to create NFTs Non-Fungible Token — Used as a metadata pattern for NFTs Metadata Views These contracts are essentially interfaces that force whoever is implementing them to add their variable and method declarations, enabling them to be interoperable with other smart contracts in the Flow ecosystem. They already exist on both testnet and mainnet networks, so be sure to import from the official addresses linked above when implementing them in your contract. You can find other core contracts . in the Flow documentation Create Tests for Your Smart Contracts, Transactions, and Scripts The is crucial for testing deployment scenarios for your contracts. However, it’s important to note that you’ll need the running in the background for full functionality. With these, you can test creating new accounts, sending transactions, running queries, executing scripts, and more. Additionally, it integrates with the Flow Emulator so you can create, run, and stop emulator instances. Flow JavaScript Testing Framework Flow CLI Add Your Contract to the Flow NFT Catalog The exists as a database to store NFT contracts and metadata. By uploading your contract, your NFTs become interoperable with the rest of the Flow ecosystem, and other developers can easily support your collection in their marketplaces or other dapps. Flow NFT Catalog You can by completing a simple four-step process. add your contract to the catalog Be Specific With Types Cadence is a and typed language that allows the developer to specify which types contain or return variables, interfaces, and functions. Therefore, you should be as specific as possible when making declarations; use generic types only in necessary situations. Not doing so can result in clumsy errors. strong statically Access Control Whenever possible, you should be deliberate with access control. Luckily, on the Flow blockchain, a caller cannot access objects in another user’s account storage without a reference to it. This is called reference-based security, and it means that nothing is truly public by default. However, when writing Cadence code, care must be taken when declaring variables, structs, functions, resources, and so on. There are four levels of access control a developer can include in their declarations: — accessible/visible in all scopes pub/access(all) — only accessible in the entire account where it’s defined (other contracts in the same account can access) access(account) — only accessible in the scope of the contract in which it is defined (cannot be accessed outside of the contract) access(contract) — only accessible in current and inner scopes priv/access(self) Avoid using pub/access(all) whenever possible. Check out the Flow documentation for . more information on access control Create StoragePath and PublicPath Constants Inside Your Contract The paths to your contract’s resources are extremely important and must be consistent across all transactions and scripts. To ensure uniformity, you should create constants for both PublicPath and StoragePath. pub contract BestPractices { pub let CollectionStoragePath: StoragePath pub let CollectionPublicPath: PublicPath init(){ self.CollectionStoragePath = /storage/bestPracticesCollection self.CollectionPublicPath = /public/bestPracticesCollection } } When you create a transaction that uses one of these paths, you only need to call its respective variable with the imported contract to reference the required path. //This script checks if a user has the public Collection from BestPractices contract import BestPractices from 0x... pub fun main(addr: Address): Bool { return getAccount(addr).getLinkTarget(BestPractices.CollectionPublicPath) != nil } Admin Resource It’s good practice to create an administrative resource that contains specific functions to perform actions under the contract, such as a mint function. This convention ensures that only accounts with that resource or capability can perform administrative functions. pub contract BestPractices { pub let CollectionStoragePath: StoragePath pub let CollectionPublicPath: PublicPath pub let AdminStoragePath: StoragePath pub resource AdminResource { pub fun mintNFT(){ //your mint NFT code here! } } init(){ self.CollectionStoragePath = /storage/bestPracticesCollection self.CollectionPublicPath = /public/bestPracticesCollection self.AdminStoragePath = /storage/bestPracticesAdmin //Create the adminResource and store it inside Contract Deployer account let adminResource <- create AdminResource() self.account.save(<- adminResource, to: self.AdminStoragePath) } } Create Custom Events Events are values that can be emitted during the execution of your Cadence code. For example, when defining important actions in your contracts, you can emit events to signal their completion or deliver a specific value. As a result, transactions that interact with your smart contracts can receive additional information through these events. pub contract BestPractices { pub let CollectionStoragePath: StoragePath pub let CollectionPublicPath: PublicPath pub let AdminStoragePath: StoragePath //Create your own events pub event AdminCreated() pub resource AdminResource { pub fun mintNFT(){ //your mint NFT code here! } } init(){ self.CollectionStoragePath = /storage/bestPracticesCollection self.CollectionPublicPath = /public/bestPracticesCollection self.AdminStoragePath = /storage/bestPracticesAdmin //Create the adminResource and store it inside Contract Deployer account let adminResource <- create AdminResource() self.account.save(<- adminResource, to: self.AdminStoragePath) } } Patterns on Cadence/Flow Blockchain Be Specific With Types in Type Constraints One of the most powerful features of the Cadence language is undoubtedly . Through capabilities, the scope of resource access expands. capabilities An important point when creating a capability is to specify which features of your resource should be available to others. This can be done at the time of link creation using type constraints. In this example, we use the to create a basic functionality where any account that wants to receive an ExampleNFT must have a collection. ExampleNFT contract import NonFungibleToken from 0x... import ExampleNFT from 0x... transaction{ prepare(acct: AuthAccount){ let collection <- ExampleNFT.createEmptyCollection() // Put the new collection in storage acct.save(<-collection, to: ExampleNFT.CollectionStoragePath) // Create a public Capability for the collection acct.link<&ExampleNFT.Collection{ExampleNFT.CollectionPublic}>(ExampleNFT.CollectionPublicPath, target: ExampleNFT.CollectionStoragePath) } } The & symbol in specifies that we are using a . After the reference symbol, we add the type to which the capability we are creating will have access. At this point, we need to be as specific as possible. &ExampleNFT reference This pattern strengthens security and limits the functionality that the user calling the function of this capability can use. borrow Omitting the type will give you access to all the functions that exist in the reference, including the withdraw function, so that anyone can access the user's collection and steal their NFTs. {ExampleNFT.CollectionPublic} ExampleNFT.Collection Use the Borrow Function To use the resource’s features, you could call the function to remove the resource from the account, use its features, and call the function to save it again. However, this approach is costly and inefficient. Load Save To avoid this, use the function instead. It allows you to use a reference to the resource you are calling. This method makes your transaction much more efficient and cost-effective. borrow Use the check and getLinkTarget Functions When building applications on the Flow blockchain, you will discover the user’s account plays a vital role. Unlike other blockchains, such as Ethereum, Flow stores resources, assets, and more directly in the user’s account rather than as a reference to an address on a public digital ledger. This approach requires the account to have a specific storage location, such as a collection or vault for fungible tokens. However, this also adds complexity. One must be sure that the user either does or does not have the collection in their account. Both the function (which checks whether a capability exists in a given path) and the function (which returns the path of a given capability) must be used when adding collections and capabilities to the user account. These functions ensure that the transaction executes without problems. check() getLinkTarget() Use Panic Panic is a built-in function in Cadence that allows you to terminate a program unconditionally. This can occur during the execution of your smart contract code and returns an error message, which makes it easier to understand when something does not go as expected. When declaring variables, it is possible to define them as optional; meaning, if they are not of the specified type, they have a value of nil. Thus, in Cadence, it is possible to use two question marks followed by the function when querying the value of a particular variable or function that returns an optional. panic("treatment message") let optionalAccount: AuthAccount? = //... let account = optionalAccount ?? panic("missing account") This command attempts to return the value with the specified type. If the returned value is the wrong type, or nil, the execution aborts, and the selected treatment message displays on the console. ??panic("treatment message") Anti-Patterns Although Cadence is designed to avoid many of the potential bugs and exploits found in other blockchain ecosystems, there are some anti-patterns developers should be aware of while building. Listed below are a few important ones to consider. For a complete list of anti-patterns, . check out the Flow documentation Failing to Specify the Type When Using the Borrow Function Developers should use the borrow function mentioned above to take advantage of the features available in a capability. However, it should be clear that users can store anything in their memory. Therefore, it is vital to make sure that what is borrowed is of the correct type. Not specifying the type is an anti-pattern that can end up causing errors or even breakage in your transaction and application. //Bad Practice. Should be avoided let collection = getAccount(address).getCapability(ExampleNFT.CollectionPublicPath) .borrow<&ExampleNFT.Collection>()?? panic("Could not borrow a reference to the nft collection") //Correct! let collection = getAccount(address).getCapability(ExampleNFT.CollectionPublicPath) .borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>() ?? panic("Could not borrow a reference to the nft collection") Using AuthAccount as a Function Parameter For a transaction in the preparation phase, it is possible to access the AuthAccount field of the user. This object allows access to the memory storage and all other private areas of the account for the user who provides it. Passing this field as an argument is not recommended and should be avoided, as cases where this method is necessary are extremely rare. //Bad Practice. Should be avoided transaction() { prepare(acct: AuthAccount){ //You take sensitive and important user data out of the scope of the transaction prepare phase ExampleNFT.function(acct: acct) } } Using Public Access Control on Dictionaries and Arrays By storing dictionaries and array variables in your contract with scope, your smart contract becomes vulnerable, as anyone can manipulate and change these values. pub/access(all) Creating Capabilities and References Using the Auth Keyword Creating capabilities and references with the keyword exposes the value to downcasting, which could provide access to functionality that was not originally intended. auth //AVOID THIS! signer.link<auth &ExampleNFT.CollectionPublic{NonFungibleToken.Receiver}>( /public/exampleNFT, target: /storage/exampleNFT ) Conclusion When developing on the Flow blockchain using Cadence, it helps to be aware of design patterns, anti-patterns, and best practices. By following these best practices, we can ensure a consistent level of security throughout the Flow ecosystem, thus providing the best experience for users. For a more thorough understanding, . read through the Flow documentation Have a really great day!