Skip to main content

4. Capability Tutorial

Overview

Open the starter code for this tutorial in the Flow Playground. It is the same code that was in the previous tutorial:

https://play.onflow.org/a7f45bcd-8fda-45f6-b443-4b77302a1687


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

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 Resource tutorial. Before beginning this tutorial, you should have an idea of how accounts,transactions,resources, and signers work with basic field types. This tutorial will build on your understanding of accounts and resources. You'll learn how to interact with resources using capabilities In Cadence, resources are a composite type like a struct or a class, but with some special rules:

  • Each instance of a resource can only exist in exactly one location and cannot be copied.
  • 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 destroyed.

Use-Cases for Capabilities and Scripts

Let's look at why you would want to use capabilities to expand access to resources in a real-world context.

A real user's account will contain functions and fields that need varying levels of access scope and privacy. For example, if you're working on an app that allows users to exchange tokens. While you definitely need to sign write access for a feature like withdrawing tokens from an account, your app should allow anybody to deposit tokens. After your user authenticates your app for the first time, you can create a capability that allows your app to withdraw tokens, this makes it more convenient to write transactions that can withdraw an account's tokens to spend or trade them.

In this tutorial, you will:

  1. Interact with the resource we created using transactions
  2. Create capabilities to extend the resource access scope
  3. Execute a script that interacts with the resource

Accessing Resources with Capabilities


Before following this tutorial, you should have the HelloWorld contract deployed in account 0x01, just like in the previous Resource contract tutorial.

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

HelloWorldResource-2.cdc

_22
pub contract HelloWorld {
_22
_22
// Declare a resource that only includes one function.
_22
pub resource HelloAsset {
_22
_22
// A transaction can call this function to get the "Hello, World!"
_22
// message from the resource.
_22
pub 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
pub fun createHelloAsset(): @HelloAsset {
_22
return <-create HelloAsset()
_22
}
_22
_22
init() {
_22
log("Hello Asset")
_22
}
_22
}

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

Click on the Create Hello transaction and send it with 0x01 as the signer.

The contract and transaction above creates and stores the resource we'll be using in this tutorial. For a more detailed breakdown of the contract, have a look at the previous tutorial.

Creating Capabilities and References to Stored Resources


You need explicit permission from the owner of an account to access its storage. Capabilities allow an account owner to grant access to specific fields and functions stored in their accounts. (Explained more below)

In this transaction, you create a new capability, then use the link function to create a public link to your HelloAsset resource object. Next you use that link to borrow a reference to the underlying object and call the hello() function. A detailed explanation of what is happening in this transaction is below the transaction code so, if you feel lost, keep reading!

Open the transaction named Create Link.


Create Link should contain the following code:

CreateLink

_38
import HelloWorld from 0x01
_38
_38
// This transaction creates a new capability
_38
// for the HelloAsset resource in storage
_38
// and adds it to the account's public area.
_38
//
_38
// Other accounts and scripts can use this capability
_38
// to create a reference to the private object to be able to
_38
// access its fields and call its methods.
_38
_38
transaction {
_38
prepare(account: AuthAccount) {
_38
_38
// Create a public capability by linking the capability to
_38
// a `target` object in account storage.
_38
// The capability allows access to the object through an
_38
// interface defined by the owner.
_38
// This does not check if the link is valid or if the target exists.
_38
// It just creates the capability.
_38
// The capability is created and stored at /public/Hello, and is
_38
// also returned from the function.
_38
let capability = account.link<&HelloWorld.HelloAsset>(/public/HelloAssetTutorial, target: /storage/HelloAssetTutorial)
_38
_38
// Use the capability's borrow method to create a new reference
_38
// to the object that the capability links to
_38
// We use optional chaining "??" to get the value because
_38
// result of the borrow could fail, so it is an optional.
_38
// If the optional is nil,
_38
// the panic will happen with a descriptive error message
_38
let helloReference = capability.borrow()
_38
?? panic("Could not borrow a reference to the hello capability")
_38
_38
// Call the hello function using the reference
_38
// to the HelloAsset resource.
_38
//
_38
log(helloReference.hello())
_38
}
_38
}

Ensure account 0x01 is still selected as a transaction signer.
Click the Send button to send the transaction.

In this transaction, we use the prepare phase to:

  1. Create a capability with the link method to the stored object HelloWorld.HelloAsset from the account path /storage/HelloAssetTutorial
  2. Store the capability in the account path /public/HelloAssetTutorial
  3. Use the borrow method to create a reference to the object we linked to called helloReference
  4. Call the hello() function using the reference we created, helloReference

You should see "Hello, World" show up in the console again. You might be confused that we were able to call a method on the HelloAsset object without actually being directly in control of it! It is also stored in the /storage/ domain of the account, which should be private.

This is because we created a capability for the HelloAsset object. Capabilities are kind of like pointers in other languages.

Capability Based Access Control

Capabilities allow the owners of objects to specify what functionality of their private objects is available to others. Think of it kind of like an account's public API, if you're familiar with the concept. The account owner has private objects stored in their storage, like their collectibles or their money, but they might still want others to be able to see what collectibles they have in their account, or they want to allow anyone to deposit more money of a certain currency in their account. Since these objects are stored in private storage by default, the owner has to do something to open up access to these while still retaining full control. We create capabilities to accomplish this.

In our example, the owner of HelloAsset might still want to let other people call the hello method. This is what capabilities are for. They represent a link to an object in an account's storage that has the type specified when the link is created.

It is important to remember that someone else who has this capability cannot move or destroy the object that the capability is linked to! They can only access fields that the owner has explicitly declared in the type specification of the link method (described below).

Capabilities do not have any meaningful functionality on their own, but every capability has a borrow method, which creates a reference to the object that the capability is linked to. This reference is used to read fields or call methods on the object they reference as if the owner of the reference had the actual object.

Note that this only allows access to fields and methods. It does not allow copying, moving, or modifying the original object directly.

Let's break down what is happening in this transaction.

First, we create a public link to the private HelloAsset object in /storage/:


_10
let capability = account.link<&HelloWorld.HelloAsset>(/public/Hello, target: /storage/Hello)

The link method returns a capability that can be used to access this link.

The HelloAsset object is stored in /storage/HelloAssetTutorial, which only the account owner can access. They want any user in the network to be able to call the hello() method. So they make a public capability in /public/HelloAssetTutorial.

To create a capability, we use the AuthAccount.link method to link a new capability to an object in storage. The type contained in <> is the restricted reference type that the capability represents. The capability says that whoever borrows a reference from this capability can only have access to the fields and methods that are specified by the type in <>. The specified type has to be a subtype of the type of the object being linked to, meaning that it cannot contain any fields or functions that the linked object doesn't have.

A reference is referred to by the & symbol. Here, the capability references the HelloAsset object, so we specify <&HelloWorld.HelloAsset> as the type, which gives access to everything in the HelloAsset object.

The first argument to the link function is the path where you want to store the link for the capability and the target argument is the path to the object in storage that is to be linked to. We always store links for capabilities in the /private/ or /public/ domains:

  • We choose /private/ if we only want to allow one or a small number of users to access it
  • We choose /public/ if we want any user in the network to be able to access it. Capabilities always link to objects in the /storage/ domain.

To borrow a reference to an object from the capability, we use the capability's borrow method.


_10
let helloReference = capability.borrow()
_10
?? panic("Could not borrow a reference to the hello capability")

This method creates the reference as the type we specified in <> in the link function. While borrowing the reference, we use optional chaining because the borrowing of the reference could fail. The reference could be nil if the targeted storage slot is empty, is already borrowed, or if the requested type exceeds what is allowed by the capability. We panic with a descriptive error message so the caller can know better what went wrong.

We separate this process into capabilities and references to protect against reentrancy attacks. A reentrancy attack is where a malicious actor could call into an object multiple times. These attacks have plagued other smart contract languages. Only one reference to an object can exist at a time, so this type of vulnerability isn't possible for objects in storage when you use Cadence.

Additionally, the owner of an object can effectively revoke capabilities they have created by moving the underlying object or destroying the link with the unlink method. If the referenced object is moved or the link is destroyed, capabilities that have been created from that link are invalidated.

You can find more detailed documentation about capabilities in the language reference.

Now, anyone can call the hello() method on your HelloAsset object by borrowing a reference with your public capability in /public/Hello! (Covered in the next section)

Lastly, we call the hello() method with our borrowed reference:


_10
// Call the hello function using the reference to the HelloAsset resource
_10
log(helloReference.hello())

At the end of the transaction execution, the helloReference value is lost, but that is ok because while it references a resource, it isn't the actual resource itself, so it is ok to lose it.

In the next section, we look at how capabilities can expand the access a script has to an account.

Executing Scripts


A script is a very simple transaction type in Cadence that cannot perform any writes to the blockchain and can only read the state of an account or contract. It runs without permission from any account, so scripts need capabilities to access account storage.

To execute a script, write a function called pub fun main(). You can click the execute script button to run the script. The result of the script will be printed to the console output.

Open the file Script1.cdc.


Script1.cdc should look like the following:

Script1.cdc

_22
import HelloWorld from 0x01
_22
_22
pub fun main() {
_22
_22
// Cadence code can get an account's public account object
_22
// by using the getAccount() built-in function.
_22
let helloAccount = getAccount(0x01)
_22
_22
// Get the public capability from the public path of the owner's account
_22
let helloCapability = helloAccount.getCapability<&HelloWorld.HelloAsset>(/public/HelloAssetTutorial)
_22
_22
// borrow a reference for the capability
_22
let helloReference = helloCapability.borrow()
_22
?? panic("Could not borrow a reference to the hello capability")
_22
_22
// The log built-in function logs its argument to stdout.
_22
//
_22
// Here we are using optional chaining to call the "hello"
_22
// method on the HelloAsset resource that is referenced
_22
// in the published area of the account.
_22
log(helloReference.hello())
_22
}

Here's what this script does:

  1. It fetches the PublicAccount object with getAccount and assigns it to the variable helloAccount
  2. Uses the getCapability method to get the capability from the Create Link transaction
  3. Borrows a reference for the capability using the borrow method and assigns it to helloReference
  4. Logs the result of the hello() function from helloReference to the console.

_10
let helloAccount = getAccount(0x01)

The PublicAccount object is available to anyone in the network for every account, but only has access to a small subset of functions that can be read from the /public/ domain in an account. Most of the account storage API is not available on the PublicAccount object, and only signed transactions can make persistent changes to the AuthAccount object. AuthAccount can now be accessed publicly in scripts with the getAuthAccount function, but any changes are discarded after the script execution. With capabilities we can make resources saved to an account explicitly available in the /public/ domain.

See the language reference for more information about accounts.

Then, the script gets the capability that was created in Create Link.


_10
// Get the public capability from the public path of the owner's account
_10
let helloCapability = helloAccount.getCapability(/public/HelloAssetTutorial)

To get a capability that is stored in an account, use the account.getCapability() function. This function is available on AuthAccounts and on PublicAccounts. getCapability() returns a capability for the link at the path that is specified, also with the type that is specified. It does not check if the target exists, so the borrow will fail if the capability is invalid.

After that, the script borrows a reference from the capability.


_10
let helloReference = helloCapability.borrow()

Then, the script uses the reference to call the hello() function and prints the result.

Let's execute the script to see it run correctly.

Click the Execute button in the playground.

You should see something like this print:


_10
> "Hello, World"
_10
> Result > "void"

Good work! Your script ran successfully.

Reviewing Capabilities

This tutorial expanded on the idea of resources in Cadence by expanding access scope to a resource using capabilities and covering more account storage API use-cases.

You deployed a smart contract with a resource, then created a capability to grant access to that resource. With the capability, you used the borrow method to create a reference and used the reference to call the resource's hello() function. Finally, you used a script to borrow the same capability and create a reference so that the script can call the resource's hello() function. This is important because script's cannot access account storage without using capabilities.

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
  • Create capabilities to grant access to resources in an account
  • Interact with resources using both signed transactions and scripts

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

You're on the right track to building more complex applications with Cadence, now is a great time to check out the Cadence Best Practices document and Anti-patterns document as your applications become more complex.