Cadence Design Patterns
This is a selection of software design patterns developed by core Flow developers while writing Cadence code for deployment to Flow Mainnet.
Design patterns are building blocks for software development. They may provide a solution to a problem that you encounter when writing smart contracts in Cadence. Remember that if they do fit they may not be the right solution for a given situation, and they are not meant to be a set of prescriptions to be followed exclusively even where a better solution represents itself.
Capability Receiver
Problem
An account must be given a capability to a resource or contract in another account but a single transaction with both accounts authorizing it cannot be easily produced. This prevents a single transaction from fetching the capability from one account and delivering it to the other.
Solution
Account B creates a resource that can receive the capability and stores this in their /storage/
area. They then expose a Capability to this in their /public/
area with a function that can receive the desired Capability and store it in the resource for later use.
Account A fetches the receiver Capability from B's /public/
area, creates the desired Capability, and passes it to the receiver function. The receiver function stores the Capability in the resource that it is on in account B's /storage/
area for later use.
There are two nuances to this workflow that are required to ensure that it is secure.
The first is that only Account A should be able to create instances of the desired Capability. This ensures that nobody else can create instances of it and call the receiver function on B's receiver capability instead of A. This means that A is probably an admin account.
The second is that the field on the receiver resource that stores the desired Capability should be access(contract) and only accessed by code within the contract that needs to. This ensures that B cannot copy and share the Capability with anyone else.
Example Code
See:
custody_setup_account_creator.cdc
admin_deposit_account_creator.cdc
Capability Revocation
Problem
A capability provided by one account to a second account must able to be revoked by the first account without the co-operation of the second.
Solution
The first account should create the capability as a link to a capability in /private/, which then links to a resource in /storage/ . That first link is then handed to the second account as the capability for them to use. This can be stored in their private storage or a Capability Receiver.
Account 1: /private/capability
→ /storage/resource
Account 2: Capability Receiver(Capability(→Account 1: /private/capability))
If the first account wants to revoke access to the resource in storage, they should delete the /private/
link that the second account's capability refers to. Capabilities use paths rather than resource identifiers, so this will break the capability.
The first account should be careful not to create another link at the same location in its private storage once the capability has been revoked, otherwise this will restore the second account's capability.
Init Singleton
Problem
An admin resource must be created and delivered to a specified account. There should not be a function to do this, as that would allow anyone to create an admin resource.
Solution
Create any one-off resources in the contract's init()
function and deliver them to an address or AuthAccount specified as an argument.
See how this is done in the LockedTokens contract init function:
and in the transaction that is used to deploy it:
Named Value Field
Problem
Your contracts, resources, and scripts all have to refer to the same value. A number, a string, a storage path. Entering these values manually in transactions and scripts is a potential source of error. See: https://en.wikipedia.org/wiki/Magicnumber(programming)
Solution
Add an access(all)
field, e.g. a Path
, to the contract responsible for the value, and set it in the contract's initializer. Then refer to that value via this public field rather than specifying it manually.
Script-Accessible Public Field
Problem
Your contract, resource or struct has a field that will need to be read and used off-chain, often in bulk.
Solution
Make sure that the field can be accessed from a script (using an Account
) rather than requiring a transaction (using an AuthAccount
). This saves the time and soon the expense required by having to read a property using a transaction. Making the field access(all)
and exposing it via a /public/
capability will allow this. Be careful not to expose any data or functionality that should be kept private when doing so.
Script-Accessible Report
Problem
Your contract, resource or struct has a resource that you wish to access fields of off-chain via a script. But scripts cannot return resources.
Solution
Declare a struct to hold the data that you wish to return from the script. Write a function that fills out the fields of this struct with the data from the resource that you wish to access. Then call this on the resource that you wish to access the fields of in a script, and return the struct from the script.
See Script-Accessible Public Field, above, for how best to expose this capability.
Example Code
pub contract AContract {
pub struct BReportStruct {
pub var c: UInt64
pub var d: string
init(c: UInt64, d: string) {
self.c = c
self.d = d
}
}
pub resource BResource {
pub var c: UInt64
pub var d: string
pub fun generateReport() BReportStruct {
return BReportStruct(c: c, d: d)
}
init(c: UInt64, d: string) {
self.c = c
self.d = d
}
}
}
...
import A from 0xA
pub fun main(): A.BReport {
let b: AContract.BResource // Borrow the resource
let report = b.generateReport()
return b
}