Skip to main content

Resources and the Move (<-) Operator

This tutorial builds your understanding of accounts and how to interact with them by introducing resources. Resources are a special type found in Cadence that are used for any virtual items, properties, or any other sort of data that are owned by an account. They can only exist in one place at a time, which means they can be moved or borrowed, but they cannot be copied.

Working with resources requires you to take a few more steps to complete some tasks, but this level of explicit control makes it nearly impossible to accidentally duplicate, break, or burn an asset.

Objectives

After completing this tutorial, you'll be able to:

  • Instantiate a resource in a smart contract with the create keyword.
  • Save, move, and load resources using the Account Storage API and the move operator (<-).
  • Use borrow to access and use a function in a resource.
  • Use the prepare phase of a transaction to load resources from account storage.
  • Set and use variables in both the prepare and execute phase.
  • Use the nil-coalescing operator (??) to panic if a resource is not found.

Resources

Resources are one of the most important and unique features in Cadence. They're a composite type, like a struct or a class in other languages, but with some special rules designed to avoid many of the traditional dangers in smart contract development. The short version is that resources can only exist in one location at a time — they cannot be copied, duplicated, or have multiple references.

Here is an example definition of a resource:


_10
access(all) resource Money {
_10
access(all) let balance: Int
_10
_10
init() {
_10
self.balance = 0
_10
}
_10
}

As you can see, it looks just like a regular struct definition. The difference, however, is in the behavior.

Resources are useful when you want to model direct ownership of an asset or an object. By direct ownership, we mean the ability to own an actual object in your storage that represents your asset, instead of just a password or certificate that allows you to access it somewhere else.

Traditional structs or classes from other conventional programming languages are not an ideal way to represent direct ownership because they can be copied. This means that a coding error can easily result in creating multiple copies of the same asset, which breaks the scarcity requirements needed for these assets to have real value.

We must consider loss and theft at the scale of a house, a car, a bank account, or even a horse. It's worth a little bit of extra code to avoid accidentally duplicating ownership of one of these properties!

Resources solve this problem by making creation, destruction, and movement of assets explicit.

Implementing a contract with resources

Open the starter code for this tutorial in the Flow Playground at play.flow.com/b999f656-5c3e-49fa-96f2-5b0a4032f4f1.

The HelloResource.cdc file contains the following code:

HelloResource.cdc

_10
access(all) contract HelloResource {
_10
// TODO
_10
}

Defining a resource

Similar to other languages, Cadence can declare type definitions within deployed contracts. A type definition is simply a description of how a particular set of data is organized. It is not a copy or instance of that data on its own.

Any account can import these definitions to interact with objects of those types.

The key difference between a resource and a struct or class is the access scope for resources:

  • Each instance of a resource can only exist in exactly one location and cannot be copied.
    • Here, location refers to account storage, a temporary variable in a function, a storage field in a contract, and so on.
  • Resources must be explicitly moved from one location to another when accessed.
  • Resources also cannot go out of scope at the end of function execution. They must be explicitly stored somewhere or explicitly destroyed.
  • A resource can only be created in the scope that it is defined in.
    • This prevents anyone from being able to create arbitrary amounts of resource objects that others have defined.

These characteristics make it impossible to accidentally lose a resource from a coding mistake.

Add a resource called HelloAsset that contains a function to return a string containing "Hello Resources!":

HelloResource.cdc

_10
access(all) contract HelloResource {
_10
access(all) resource HelloAsset {
_10
// A transaction can call this function to get the "Hello Resources!"
_10
// message from the resource.
_10
access(all) view fun hello(): String {
_10
return "Hello Resources!"
_10
}
_10
}
_10
}

A few notes on this function:

  • access(all) makes the function publicly accessible.
  • view indicates that the function does not modify state.
  • The function return type is a String.
  • The function is not present on the contract itself and cannot be called by interacting with the contract.
warning

If you're used to Solidity, you'll want to take note that the view keyword in Cadence is used in the same cases as both view and pure in Solidity.

Creating a resource

The following steps show you how to create a resource with the create keyword and the move operator (<-).

You use the create keyword to initialize a resource. Resources can only be created by the contract that defines them and must be created before they can be used.

The move operator <- is used to move a resource — you cannot use the assignment operator =. When you initialize them or assign then to a new variable, you use the move operator <- to literally move the resource from one location to another. The old variable or location that was holding it will no longer be valid after the move.

  1. Create a resource called first_resource:

    _10
    // Note the `@` symbol to specify that it is a resource
    _10
    var first_resource: @AnyResource <- create AnyResource()

  2. Move the resource:

    _10
    var second_resource <- first_resource

    The name first_resource is no longer valid or usable:

    _10
    // Bad code, will generate an error
    _10
    var third_resource <- first_resource

  3. Add a function called createHelloAsset that creates and returns a HelloAsset resource:
    HelloResource.cdc

    _10
    access(all) fun createHelloAsset(): @HelloAsset {
    _10
    return <-create HelloAsset()
    _10
    }

    • Unlike the hello() function, this function does exist on the contract and can be called directly. Doing so creates an instance of the HelloAsset resource, moves it through the return of the function to the location calling the function — the same as you'd expect for other languages.
    • Remember, when resources are referenced, the @ symbol is placed at the beginning. In the function above, the return type is a resource of the HelloAsset type.
  4. Deploy this code to account 0x06 by clicking the Deploy button.

Creating a Hello transaction

The following shows you how to create a transaction that calls the createHelloAsset() function and saves a HelloAsset resource to the account's storage.

Open the transaction named Create Hello, which contains the following code:

create_hello.cdc

_10
import HelloResource from 0x06
_10
_10
transaction {
_10
// TODO
_10
}

We've already imported the HelloResource contract for you and stubbed out a transaction. Unlike the transaction in Hello World, you will need to modify the user's account, which means you will need to use the prepare phase to access and modify the account that is going to get an instance of the resource.

Prepare phase

To prepare:

  1. Create a prepare phase with the SaveValue authorization entitlement to the user's account.

  2. Use create to create a new instance of the HelloAsset.

  3. Save the new resource in the user's account.

  4. Inside the transaction, stub out the prepare phase with the authorization entitlement:


    _10
    import HelloResource from 0x06
    _10
    _10
    transaction {
    _10
    prepare(acct: auth(SaveValue) &Account) {
    _10
    // TODO
    _10
    }
    _10
    }

  5. Use the createHelloAsset function in HelloResource to create an instance of the resource inside of the prepeare and move it into a constant:


    _10
    let newHello <- HelloResource.createHelloAsset()

You'll get an error for loss of resource, which is one of the best features of Cadence! The language prevents you from accidentally destroying a resource at the syntax level.

Storage paths

In Cadence Accounts, objects are stored in paths. Paths represent a file system for user accounts, where an object can be stored at any user-defined path. Usually, contracts will specify for the user where objects from that contract should be stored. This enables any code to know how to access these objects in a standard way.

Paths start with the character /, followed by the domain, the path separator /, and finally the identifier. The identifier must start with a letter and can only be followed by letters, numbers, or the underscore _. For example, the path /storage/test has the domain storage and the identifier test.

There are two valid domains: storage and public.

Paths in the storage domain have type StoragePath, and paths in the public domain have the type PublicPath. Both StoragePath and PublicPath are subtypes of Path.

Paths are not strings and do not have quotes around them.

Use the account reference with the SaveValue authorization entitlement to move the new resource into storage located in /storage/HelloAssetTutorial:


_10
acct.storage.save(<-newHello, to: /storage/HelloAssetTutorial)

The first parameter in save is the object that is being stored, and the to parameter is the path that the object is being stored at. The path must be a storage path, so only the domain /storage/ is allowed in the to parameter.

Notice that the error for loss of resource has been resolved.

If there is already an object stored under the given path, the program aborts. Remember, the Cadence type system ensures that a resource can never be accidentally lost. When moving a resource to a field, into an array, into a dictionary, or into storage, there is the possibility that the location already contains a resource.

Cadence forces the developer to explicitly handle the case of an existing resource so that it is not accidentally lost through an overwrite.

It is also very important when choosing the name of your paths to pick an identifier that is very specific and unique to your project.

Currently, account storage paths are global, so there is a chance that projects could use the same storage paths, which could cause path conflicts! This could be a headache for you, so choose unique path names to avoid this problem.

Execute phase

Use the execute phase to log a message that the resource was successfully saved:


_10
execute {
_10
log("Saved Hello Resource to account.")
_10
}

We'll learn more realistic uses of this phase soon.

You should have something similar to:


_12
import HelloResource from 0x06
_12
_12
transaction {
_12
prepare(acct: auth(SaveValue) &Account) {
_12
let newHello <- HelloResource.createHelloAsset()
_12
acct.storage.save(<-newHello, to: /storage/HelloAssetTutorial)
_12
}
_12
_12
execute {
_12
log("Saved Hello Resource to account.")
_12
}
_12
}

This is our first transaction using the prepare phase!

The prepare phase is the only place that has access to the signing account, via account references (&Account).

Account references have access to many different methods that are used to interact with an account, such as to save a resource to the account's storage.

By not allowing the execute phase to access account storage and using entitlements, we can statically verify which assets and areas/paths of the signers' account a given transaction can modify.

Browser wallets and applications that submit transactions for users can use this to show what a transaction could alter, giving users information about transactions that wallets will be executing for them, and confidence that they aren't getting fed a malicious or dangerous transaction from an app or wallet.

To execute:

  1. Select account 0x06 as the only signer.
  2. Click the Send button to submit the transaction. You'll see in the log:

    _10
    "Saved Hello Resource to account."

  3. Use Send to send the transaction again from account 0x06 You'll now get an error, because there's already a resource in /storage/HelloAssetTutorial:

    _10
    execution error code 1: [Error Code: 1101] error caused by: 1 error occurred:
    _10
    * transaction execute failed: [Error Code: 1101] cadence runtime error: Execution failed:
    _10
    error: failed to save object: path /storage/HelloAssetTutorial in account 0x0000000000000009 already stores an object
    _10
    --> 805f4e247a920635abf91969b95a63964dcba086bc364aedc552087334024656:19:8
    _10
    |
    _10
    19 | acct.storage.save(<-newHello, to: /storage/HelloAssetTutorial)
    _10
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  4. Remove the line of code that saves newHello to storage.
    • Again, you'll get an error for newHello that says loss of resource. This means that you are not handling the resource properly. Remember that if you ever see this error in any of your programs, it means there is a resource somewhere that is not being explicitly stored or destroyed. Add the line back before you forget!

Review storage

Now that you have executed the transaction, account 0x06 has the newly created HelloWorld.HelloAsset resource stored in its storage. You can verify this by clicking on account 0x06 on the bottom left. This opens a view of the different contracts and objects in the account.

The resource you created appears in Account Storage:


_49
{
_49
"value": [
_49
{
_49
"key": {
_49
"value": "value",
_49
"type": "String"
_49
},
_49
"value": {
_49
"value": {
_49
"id": "A.0000000000000006.HelloResource.HelloAsset",
_49
"fields": [
_49
{
_49
"value": {
_49
"value": "269380348805120",
_49
"type": "UInt64"
_49
},
_49
"name": "uuid"
_49
}
_49
]
_49
},
_49
"type": "Resource"
_49
}
_49
},
_49
{
_49
"key": {
_49
"value": "type",
_49
"type": "String"
_49
},
_49
"value": {
_49
"value": "A.0000000000000006.HelloResource.HelloAsset",
_49
"type": "String"
_49
}
_49
},
_49
{
_49
"key": {
_49
"value": "path",
_49
"type": "String"
_49
},
_49
"value": {
_49
"value": {
_49
"domain": "storage",
_49
"identifier": "HelloAssetTutorial"
_49
},
_49
"type": "Path"
_49
}
_49
}
_49
],
_49
"type": "Dictionary"
_49
}

You'll also see FlowToken objects and the HelloResource Contract.

Run the transaction from account 0x07 and compare the differences between the accounts.

Check for existing storage

In real applications, you need to check the location path you are storing in to make sure both cases are handled properly.

  1. Update the authorization entitlement in the prepare phase to include BorrowValue:


    _10
    prepare(acct: auth(BorrowValue, SaveValue) &Account) {
    _10
    // Existing code...
    _10
    }

  2. Add a transaction-level (similar to contract-level or class-level) variable to store a result String.

    • Similar to a class-level variable in other languages, these go at the top, inside the transaction scope, but not inside anything else. They are accessible in both the prepare and execute statements of a transaction:

    _10
    import HelloResource from 0x06
    _10
    _10
    transaction {
    _10
    var result: String
    _10
    // Other code...
    _10
    }

    • You'll get an error: missing initialization of field 'result' in type 'Transaction'. not initialized
    • In transactions, variables at the transaction level must be initialized in the prepare phase.
  3. Initialize the result message and create a constant for the storage path:


    _10
    self.result = "Saved Hello Resource to account."
    _10
    let storagePath = /storage/HelloAssetTutorial

warning

In Cadence, storage paths are a type. They are not Strings and are not enclosed by quotes.

One way to check whether or not a storage path has an object in it is to use the built-in storage.check function with the type and path. If the result is true, then there is an object in account storage that matches the type requested. If it's false, there is not.

warning

A response of false does not mean the location is empty. If you ask for an apple and the location contains an orange, this function will return false.

This is not likely to occur because projects are encouraged to create storage and public paths that are very unique, but is theoretically possible if projects don't follow this best practice or if there is a malicious app that tries to store things in other projects' paths.

Depending on the needs of your app, you'll use this pattern to decide what to do in each case. For this example, we'll simply use it to change the log message if the storage is in use or create and save the HelloAsset if it is not.

  1. Refactor your prepare statement to check and see if the storage path is in use. If it is, update the result message. Otherwise, create and save a HelloAsset:


    _10
    if acct.storage.check<&HelloResource.HelloAsset>(from: storagePath) {
    _10
    self.result = "Unable to save, resource already present."
    _10
    } else {
    _10
    let newHello <- HelloResource.createHelloAsset()
    _10
    acct.storage.save(<-newHello, to: storagePath)
    _10
    }

    • When you [check] a resource, you must put the type of the resource to be borrowed inside the <> after the call to borrow, before the parentheses. The from parameter is the storage path to the object you are borrowing.
  2. Update the log in execute to use self.result instead of the hardcoded string:


    _10
    execute {
    _10
    log(self.result)
    _10
    }

    You should end up with something similar to:


    _21
    import HelloResource from 0x06
    _21
    _21
    transaction {
    _21
    var result: String
    _21
    _21
    prepare(acct: auth(BorrowValue, SaveValue) &Account) {
    _21
    self.result = "Saved Hello Resource to account."
    _21
    let storagePath = /storage/HelloAssetTutorial
    _21
    _21
    if acct.storage.check<&HelloResource.HelloAsset>(from: storagePath) {
    _21
    self.result = "Unable to save, resource already present."
    _21
    } else {
    _21
    let newHello <- HelloResource.createHelloAsset()
    _21
    acct.storage.save(<-newHello, to: storagePath)
    _21
    }
    _21
    }
    _21
    _21
    execute {
    _21
    log(self.result)
    _21
    }
    _21
    }

  3. Use Send to send the transaction again, both with accounts that have and have not yet created and stored an instance of HelloAsset.

Now you'll see an appropriate log whether or not a new resource was created and saved.

Loading a Hello transaction

The following shows you how to use a transaction to call the hello() method from the HelloAsset resource.

  1. Open the transaction named Load Hello, which is empty.

  2. Stub out a transaction that imports HelloResource and passes in an account reference with the BorrowValue authorization entitlement, which looks something like this:

    load_hello.cdc

    _10
    import HelloResource from 0x06
    _10
    _10
    transaction {
    _10
    _10
    prepare(acct: auth(BorrowValue) &Account) {
    _10
    // TODO
    _10
    }
    _10
    }

    • You just learned how to borrow a reference to a resource. You could use an if statement to handle the possibility that the resource isn't there, but if you want to simply terminate execution, a common practice is to combine a panic statement with the nil-coalescing operator (??).
    • This operator executes the statement on the left side. If that is nil, the right side is evaluated and returned. In this case, the return is irrelevant, because we're going to cause a panic and terminate execution.
  3. Create a variable with a reference to the HelloAsset resource stored in the user's account. Use panic if this resource is not found:


    _10
    let helloAsset = acct.storage.borrow<&HelloResource.HelloAsset>(from: /storage/HelloAssetTutorial)
    _10
    ?? panic("The signer does not have the HelloAsset resource stored at /storage/HelloAssetTutorial. Run the `Create Hello` Transaction to store the resource")

  4. Use log to log the return from a call to the hello() function.

danger

Borrowing a reference does not allow you to move or destroy a resource, but it does allow you to mutate data inside that resource via one of the resource's functions.

Your transaction should be similar to:


_10
import HelloResource from 0x06
_10
_10
transaction {
_10
prepare(acct: auth(BorrowValue, LoadValue, SaveValue) &Account) {
_10
let helloAsset = acct.storage.borrow<&HelloResource.HelloAsset>(from: /storage/HelloAssetTutorial)
_10
?? panic("The signer does not have the HelloAsset resource stored at /storage/HelloAssetTutorial. Run the `Create Hello` Transaction again to store the resource")
_10
_10
log(helloAsset.hello())
_10
}
_10
}

In Cadence, we have the resources to leave very detailed error messages. Check out the error messages in the Non-Fungible Token Contract and Generic NFT Transfer transaction in the Flow NFT GitHub repo for examples of production error messages.

Test your transaction with several accounts to evaluate all possible cases.

Reviewing the resource contract

In this tutorial you learned how to create resources in Cadence. You implemented a smart contract that is accessible in all scopes. The smart contract has a resource declared that implemented a function called hello(), that returns the string "Hello, World!". It also declares a function that can create a resource.

Next, you implemented a transaction to create the resource and save it in the account calling it.

Finally, you used a transaction to borrow a reference to the HelloAsset resource from account storage and call the hello method

Now that you have completed the tutorial, you can:

  • Instantiate a resource in a smart contract with the create keyword.
  • Save, move, and load resources using the Account Storage API and the move operator (<-).
  • Use borrow to access and use a function in a resource.
  • Use the prepare phase of a transaction to load resources from account storage.
  • Set and use variables in both the prepare and execute phase.
  • Use the nil-coalescing operator (??) to panic if a resource is not found.

Reference Solution

warning

You are not saving time by skipping the reference implementation. You'll learn much faster by doing the tutorials as presented!

Reference solutions are functional, but may not be optimal.