msg․sender Considered Harmful
One question that every Solidity user asks when they start programming in Cadence is "how do I check msg.sender
?". On Ethereum, checking msg.sender
is used to identify the account that is calling a function and to modify that function's behaviour appropriately. Doing so is key to identity, permissions, ownership and security on Ethereum.
Cadence does not have msg.sender
and there is no transaction-level way for Cadence code to uniquely identify its caller, not least because each transaction can be signed by more than one account. This then translates to the transaction having access to all of the signers' accounts. A further difference from Ethereum is that while Ethereum and Flow both have accounts that can contain contract code and data, in Cadence the resources that individual users own (such as NFTs) are placed in the user account's storage area rather than the contract account's.
All of this means that a task that would be implemented by a single central contract checking msg.sender
in Solidity will require a different approach in Cadence. The design of Cadence is intentionally different, it follows a Capability-based security model instead of an Access-Control List-based model. We believe that this design offers distinct advantages for code robustness and security. This article describes how to perform some common tasks in idiomatic ways that exploit those advantages and also describes some ways of approaching the same tasks that should be avoided.
Patterns
Admin Rights
Admin facilities should be contained in Admin resources. This can be a single resource with capabilities to it provided through different interfaces to expose different functionality for different roles, or it can be different resources for each role.
This is described in the Patterns document:
https://docs.onflow.org/cadence/design-patterns#init-singleton
A good example of this is minting tokens:
https://github.com/onflow/kitty-items/KittyItems.cdc
Where access to admin functionality must be given to several different accounts and/or be revocable, the Capability Receiver pattern supports this.
This is described in the Patterns document:
https://docs.onflow.org/cadence/design-patterns#capability-receiver
https://docs.onflow.org/cadence/design-patterns#capability-revocation
Allow/Block Listing
Limiting a user's control of resources that they own except in exceptional circumstances is considered un-Flow-like. If you must implement allow/block listing of accounts for regulatory compliance, route calls from functions in your resources through access(contract)
code on their contract that checks an admin-controlled dictionary containing the information required to check for allowed or blocked accounts.
This code could check the resource owner, but doing so is an antipattern (see below) and should not be used as it cannot be relied on. It is better to use an object's uuid
:
https://docs.onflow.org/cadence/language/composite-types/#resource-identifier
For example: [forthcoming]
It is important to note that the uuid does not identify the owner, and that resources can be transferred to different owners, and moved to a different path within the same user's storage or replaced by a different resource at the same path.
Operator/Allowance
Giving another user temporary partial control of resources should be implemented via private capabilities.
Direct Capabilities
Limiting access to the correct resources can be achieved by (e.g.) creating a new Vault containing only the allowance amount, or creating a new Collection containing only the NFTs that the other user is the operator for.
Limiting access to the correct functionality can be achieved by providing the other user with a capability constrained to the desired interface.
The capability can be revoked to remove the ability when required.
Revokable Capabilities are described in the Patterns document:
https://docs.onflow.org/cadence/design-patterns#capability-revocation
And they are discussed in the Tutorial:
Wrapped Capabilities
Alternatively, a capability on the original resource (Vault, Collection, etc.) can be wrapped in a resource that enforces all of these limits, and then this (or, preferably, a capability to it) passed to the other user.
For example, see KittyItemsMarket's carefully constrained use of a NonFungibleToken.Provider:
https://github.com/onflow/kitty-items/cadence/contracts/KittyItemsMarket.cdc
Ownership
If an account's storage contains a resource (such as an NFT, and NFT Collection, or an FT Vault), that account owns it. There is no need to record this anywhere else. It can be checked through public capabilities. If the user removes the public capabilities, that is their choice.
For example the Collection resource in the NFT standard, its interfaces, and the Capabilities to it placed in a user's storage:
https://github.com/onflow/flow-nft/contracts/ExampleNFT.cdc
Custodial NFT marketplaces have temporary ownership of a resource. They should provide the ability to identify the token's original owner and to return it to them if it is not sold.
For example: [forthcoming]
User Profiles
User profiles can be implemented as resources placed in the storage of the user's account, with read access via a public capability.
Admin control of user profiles, where appropriate, can be implemented using private capabilities, access(contract)
code, or using types within the contract that can only be create
d by the admin as function arguments.
For example: [forthcoming]
Antipatterns
Checking Contract.account
Contracts have the member variable let account: Account
, which is the account in which the contract is deployed:
https://docs.onflow.org/cadence/language/contracts/
This is of limited use in replacing msg.sender
, as it is essentially a tautology on Flow because contracts are deployed to the account to which you deploy them.
Checking Resource.owner
Resources that are in storage (but not those that are located in-memory, e.g. when a resource has just been created) have the implicit field let owner: PublicAccount?
:
https://docs.onflow.org/cadence/language/composite-types/#resource-owner
This can be defeated by using a newly created resource, as the owner will then be nil.
Passing An AuthAccount Into Contract Code
You should never require that the user passes an AuthAccount into your contract code for any reason. This includes for authentication purposes.
AuthAccounts provide unlimited access to a user's account, including full access to the account's public keys and all of its storage. This can be constrained in transactions but it is harder to reason about when passed into contract code.
The only exception to this is where you are deploying your own contract to an account that you control and wish to deposit a resource into a separate account. In this instance, you can pass an AuthAccount into the initializer. But this introduces a co-ordination problem in that you must get signatures from both accounts. Either depositing the resource into the contract's storage and using a separate transaction to move it to the other account, or the capability receiver pattern, are just as effective:
https://docs.onflow.org/cadence/design-patterns#capability-receiver