MAINNET:
Loading...
TESTNET:
Loading...
/
onflow.org
Flow Playground

msg․sender Considered Harmful


One question that every Solidity user asks when they start programming in Cadence is

"How do I get the account who authorized the transaction?"

In the Ethereum world, this account is referred to as msg.sender. On Ethereum, checking msg.sender is used to modify a function's behaviour depending on who authorized it. 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.

This design offers distinct advantages for code robustness and security.

This article describes how to perform some common tasks in idiomatic ways that use 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 Design Patterns document.

A good example of this is minting tokens.

Where access to admin functionality must be given to several different accounts and/or be revocable, the Capability Receiver pattern supports this.

The Design Patterns document describes both Capability Receivers and 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 a resource's uuid field.

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 is described in the Design Patterns document.

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:

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.

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 created by the admin as function arguments.

Antipatterns

Checking Contract.account

Contracts have the member variable let account: Account, which is the account in which the contract is deployed:

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?

This can be defeated by using a newly created resource, as the owner will then be nil.