Skip to main content
Version: 1.0

3. Resource Contract Tutorial

Overview

tip

Open the starter code for this tutorial in the Flow Playground:

https://play.onflow.org/b70199ae-6488-4e58-ae58-9f4ffecbd66a

The tutorial will ask you to take various actions to interact with this code.

info

The playground code that is linked uses Cadence 0.42, but the examples use Cadence 1.0 to show how each contract, transaction and script is implemented in Cadence 1.0. You can access a Cadence 1.0-compatible playground by going to https://v1.play.flow.com/. The project link will still work with the current version of the playground, but when the playground is updated to Cadence 1.0, the link will be replaced with a 1.0-compatible version.

info

Instructions that require you to take action are always included in a callout box like this one. These highlighted actions are all that you need to do to get your code running, but reading the rest is necessary to understand the language's design.

This tutorial builds on the previous Hello World tutorial. Before beginning this tutorial, you should understand :

This tutorial will build on your understanding of accounts and how to interact with them by introducing resources.

Resources are one of Cadence's most important features.

In Cadence, resources are a composite type like a struct or a class in other languages, but with some special rules.

Here is an example definition of a resource:


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

See, it looks just like a regular struct definition! The difference 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 that represents your asset, instead of just a password or certificate that allows you to access it. 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 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 have to consider loss and theft at the scale of a house, a car, or a bank account with millions of dollars, or a horse. Resources, in turn, solve this problem by making creation, destruction, and movement of assets explicit.

In this tutorial, you will:

  1. Deploy a contract that declares a resource
  2. Save the resource into the account storage
  3. Interact with the resource we created using a transaction

Implementing a Contract with Resources


To interact with resources, you'll learn a few important concepts:

Let's start by looking at how to create a resource with the create keyword and the move operator <-.

You use the create keyword used 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 into a variable. You cannot use the assignment operator = with resources, so when you initialize them or assign then to a new variable, you will need to use the move operator <-. This indicates that the resource is literally being moved from one place to another. The old variable or location that was holding it will no longer be valid after the move. This is one of the ways that Cadence ensures that any given resource only exists in one place at a time.

info

Open the Account 0x01 tab with file named HelloWorldResource.cdc.
HelloWorldResource.cdc should contain the following code:

HelloWorldResource.cdc

_22
access(all)
_22
contract HelloWorld {
_22
_22
// Declare a resource that only includes one function.
_22
access(all)
_22
resource HelloAsset {
_22
_22
// A transaction can call this function to get the "Hello, World!"
_22
// message from the resource.
_22
access(all)
_22
view fun hello(): String {
_22
return "Hello, World!"
_22
}
_22
}
_22
_22
// We're going to use the built-in create function to create a new instance
_22
// of the HelloAsset resource
_22
access(all)
_22
fun createHelloAsset(): @HelloAsset {
_22
return <-create HelloAsset()
_22
}
_22
}

info

Deploy this code to account 0x01 using the Deploy button.

We start by declaring a new HelloWorld contract in account 0x01, inside this new HelloWorld contract we:

  1. Declare the resource HelloAsset with public scope access(all)
  2. Declare the resource function hello() inside HelloAsset with public scope access(all)
  3. Declare the contract function createHelloAsset() which creates a HelloAsset resource
  4. The createHelloAsset() function uses the move operator (<-) to return the resource

This is another example of what we can do with a contract. 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.

This contract that we just deployed declares a definition for the HelloAsset resource and a function to create the resource.

Let's walk through this contract in more detail, starting with the resource. Resources are one of the most important things that Cadence introduces to the smart contract design experience:


_10
access(all)
_10
resource HelloAsset {
_10
access(all)
_10
view fun hello(): String {
_10
return "Hello, World!"
_10
}
_10
}

Resources

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, etc.
  • 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.

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

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.

The Move Operator (<-)

In this example, we declared a function that can create HelloAsset resources:


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

The @ symbol specifies that it is a resource of the type HelloAsset, which we defined in the contract. This function uses the move operator to create a resource of type HelloAsset and return it. To create a new resource object, we use the create keyword

Here we use the <- symbol. This is the move operator. The move operator <- replaces the assignment operator = in assignments that involve resources. To make the assignment of resources explicit, the move operator <- must be used when:

  • the resource is the initial value of a constant or variable,
  • the resource is moved to a different variable in an assignment,
  • the resource is moved to a function as an argument
  • the resource is returned from a function.

When a resource is moved, the old location is invalidated, and the object moves into the context of the new location.

So if I have a resource in the variable first_resource, like so:


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

and I want to assign it to a new variable, second_resource, after I do the assignment, first_resource is invalid because the underlying resource has been moved to the new variable.


_10
var second_resource <- first_resource
_10
// first_resource is now invalid. Nothing can be done with it

Regular assignments of resources are not allowed because assignments only copy the value. Resources can only exist in one location at a time, so movement must be explicitly shown in the code by using the move operator <-.

Create Hello Transaction

Now we're going to use a transaction to that calls the createHelloAsset() function and saves a HelloAsset resource to the account's storage.

info

Open the transaction named Create Hello.


Create Hello should contain the following code:

create_hello.cdc

_24
// create_hello.cdc
_24
// This transaction calls the createHelloAsset() function from the contract
_24
// to create a resource, then saves the resource in account storage using the "save" method.
_24
import HelloWorld from 0x01
_24
_24
transaction {
_24
_24
/// `auth(SaveValue) &Account` means that it is an account object
_24
/// that has the `SaveValue` authorization entitlement, which means
_24
/// that this transaction can't do anything with the &Account object
_24
/// besides saving values to storage.
_24
prepare(acct: auth(SaveValue) &Account) {
_24
// Here we create a resource and move it to the variable newHello,
_24
// then we save it in the account storage
_24
let newHello <- HelloWorld.createHelloAsset()
_24
_24
acct.storage.save(<-newHello, to: /storage/HelloAssetTutorial)
_24
}
_24
_24
// In execute, we log a string to confirm that the transaction executed successfully.
_24
execute {
_24
log("Saved Hello Resource to account.")
_24
}
_24
}

Here's what this transaction does:

  1. Import the HelloWorld definitions from account 0x01
  2. Uses the createHelloAsset() function to create a resource and move it to newHello
  3. save the created resource in the account storage of the account that signed the transaction at the path /storage/HelloAssetTutorial
  4. log the text Saved Hello Resource to account. to the console.

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 and account, e.g., the account's storage. In this case, the transaction uses auth(SaveValue) &Account. This means that it is an account object that has the SaveValue authorization entitlement, which means that this transaction can't do anything with the &Account object besides saving values to storage. We'll cover entitlements in more detail in a later tutorial. You can go to the entitlements documentation to learn more about them though.

You can also see the documentation for all of the possible account entitlements in the account section of the language reference. In this tutorial, we'll be using account functions to save to and load from account storage (/storage/).

Accounts store objects at paths. Paths basically represent a file system for your account, where an object can be stored at any user-defined path. Often, 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.

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.

Let's go over the transaction in more detail. To create a HelloAsset resource, we accessed the function createHelloAsset() from our contract, and moved the resource it created to the variable newHello.


_10
let newHello <- HelloWorld.createHelloAsset()

Next, we save the resource to the account storage. We use the account storage API to interact with the account storage in Flow. To save the resource, we'll be using the save() method from the account storage API to store the resource in the account at the path /storage/HelloAssetTutorial.


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

The first parameter to 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.

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 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.

Finally, in the execute phase we log the phrase "Saved Hello Resource to account." to the console.


_10
log("Saved Hello Resource to account.")

info

Select account 0x01 as the only signer. Click the Send button to submit the transaction.

You should see something like this:


_10
"Saved Hello Resource to account."

info

You can also try removing the line of code that saves newHello to storage.


You should see an error for newHello that says loss of resource. This means that you are not handling the resource properly. 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, meaning the program is invalid.


Add the line back to make the transaction check properly.

In this case, this is the first time we have saved anything with the selected account, so we know that the storage spot at /storage/HelloAssetTutorial is empty. In real applications, we would likely perform necessary checks and actions with the location path we are storing in to make sure we don't abort a transaction because of an accidental overwrite.

Now that you have executed the transaction, account 0x01 should have the newly created HelloWorld.HelloAsset resource stored in its storage. You can verify this by clicking on account 0x01 on the bottom left. This should open a view of the different contracts and objects in the account. You should see this entry for the HelloWorld contract and the HelloAsset resource:


_34
Deployed Contracts:
_34
[
_34
{
_34
"contract": "HelloWorld",
_34
"height": 6
_34
}
_34
]
_34
Account Storage:
_34
{
_34
"Private": null,
_34
"Public": {},
_34
"Storage": {
_34
"HelloAssetTutorial": {
_34
"Fields": [
_34
39
_34
],
_34
"ResourceType": {
_34
"Fields": [
_34
{
_34
"Identifier": "uuid",
_34
"Type": {}
_34
}
_34
],
_34
"Initializers": null,
_34
"Location": {
_34
"Address": "0x0000000000000005",
_34
"Name": "HelloWorld",
_34
"Type": "AddressLocation"
_34
},
_34
"QualifiedIdentifier": "HelloWorld.HelloAsset"
_34
}
_34
}
_34
}
_34
}

You'll also see FlowToken objects. Every account is automatically initialized with the ability to use FlowToken assets. You don't have to worry about those for now.

Load Hello Transaction

Now we're going to use a transaction to call the hello() method from the HelloAsset resource.

info

Open the transaction named Load Hello.


Load Hello should contain the following code:

load_hello.cdc

_24
import HelloWorld from 0x01
_24
_24
// This transaction calls the "hello" method on the HelloAsset object
_24
// that is stored in the account's storage by removing that object
_24
// from storage, calling the method, and then putting it back in storage
_24
_24
transaction {
_24
_24
/// In this prepare block, we have to load a value from storage
_24
/// in addition to saving it, so we also need the `LoadValue` authorization entitlement
_24
prepare(acct: auth(LoadValue, SaveValue) &Account) {
_24
_24
// Load the resource from storage, specifying the type to load it as
_24
// and the path where it is stored
_24
let helloResource <- acct.storage.load<@HelloWorld.HelloAsset>(from: /storage/HelloAssetTutorial)
_24
?? panic("The signer does not have the HelloAsset resource stored at /storage/HelloAssetTutorial")
_24
_24
// log the hello world message
_24
log(helloResource.hello())
_24
_24
// Put the resource back in storage at the same spot
_24
acct.storage.save(<-helloResource, to: /storage/HelloAssetTutorial)
_24
}
_24
}

Here's what this transaction does:

  1. Import the HelloWorld definitions from account 0x01
  2. Moves the HelloAsset object from storage to helloResource with the move operator and the load function from the account storage API
  3. Calls the hello() function of the HelloAsset resource stored in helloResource and logs the result
  4. Saves the resource in the account that we originally moved it from at the path /storage/HelloAssetTutorial

We're going to be using the prepare phase again to load the resource using the reference to the account that is passed in.

Let's go over the transaction in more detail. To remove an object from storage, we use the load method from the account storage API


_10
let helloResource <- acct.storage.load<@HelloWorld.HelloAsset>(from: /storage/HelloAssetTutorial)

If there is an object of the specified type at the path, the function returns that object and the account storage will no longer contain an object under the given path.

The type parameter for the object type to load is contained in <>. In this case, we're basically saying that we expect to load a @HelloWorld.HelloAsset resource object from this path. A type argument for the parameter must be provided explicitly. (Note the @ symbol to specify that it is a resource)

The path from must be a storage path, so only the domain /storage/ is allowed.

If no object of the specified type is stored under the given path, the function returns nothing, or nil. (This is an Optional,

Optionals are values that are able to represent either the presence or the absence of a value. Optionals have two cases: either there is a value of the specified type, or there is nothing (nil). An optional type is declared using the ? suffix.


_10
let newResource: HelloAsset? // could either have a value of type `HelloAsset`
_10
// or it could have a value of `nil`, which represents nothing

Optionals allow developers to account for nil cases more gracefully. Here, we explicitly have to account for the possibility that the helloResource object we got with load is nil (because load will return nil if there is nothing there to load).

We use the nil-coalescing operator (??) to "unwrap" the optional. This basically means that we are handling the case where the load method returns nil. If it returns nil. We panic, which will abort execution of the transaction with an error message. It is important for developers to always provide detailed error messages so that if something goes wrong in the code, it is obvious what needs to be fixed.

Refer to Optionals In Cadence to learn more about optionals and how they are used.

Next, we call the hello() function and log the output.


_10
log(helloResource.hello())

Next, we use save again to put the object back in storage in the same spot:


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

info

Select account 0x01 as the only signer. Click the Send button to submit the transaction.

You should see something like this:


_10
"Hello, World!"

Reviewing the Resource Contract

This tutorial covered an introduction to resources in Cadence, using the account storage API and interacting with resources using transactions.

You implemented a smart contract that is accessible in all scopes. The smart contract had a resource declared that implemented a function called hello() that returns the string "Hello, World!" and declared a function that can create a resource.

Next, you deployed this contract in an account and implemented a transaction to create the resource in the smart contract and save it in the account 0x01 by using it as the signer for this transaction.

Finally, you used a transaction to move the HelloAsset resource from account storage, call the hello method, and return it to the account storage.

Now that you have completed the tutorial, you have the basic knowledge to write a simple Cadence program that can:

  • Implement a resource in a smart contract
  • Save, move, and load resources using the account storage API and the move operator <-
  • Use the prepare phase of a transaction to load resources from account storage

Feel free to modify the smart contract to create different resources, experiment with the available account storage API, and write new transactions that execute different functions from your smart contract. Have a look at the resource reference page to find out more about what you can do with resources.