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.
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 Cadence 1.0. The 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.
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 want to make a feature like withdrawing tokens from an account only accessible by the owner of the tokens, your app should allow anybody to deposit tokens.
Capabilities are what allows for this detailed control of owned assets. They allow a user to indicate which of the functionality of their account should be accessible to themselves, their trusted friends, and the public.
For example, a user might want to allow a friend of theirs to use some of their money to spend, in this case, they could create a capability that gives the friend access to only this part of their account, instead of having to give full control over.
Or if a user authenticates a trading app for the first time, they could ask the user for a capability object that allows the app to access the trading functionality of a user's account so that the app doesn't need to ask the user for a signature every time.
In this tutorial, you will:
- Interact with the resource we created using transactions
- Create capabilities to extend the resource access scope
- 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:
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 issue a new capability using the issue
function.
This create a link to your HelloAsset
resource object.
Then you publish that link to your account's public space, so others can access it.
Next, anyone can 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:
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:
- Create a capability with the
account.capabilities.storage.issue
method to the stored objectHelloWorld.HelloAsset
from the account path/storage/HelloAssetTutorial
- Publish the capability to the account path
/public/HelloAssetTutorial
- Use the
borrow
method to create a reference to the object we linked to calledhelloReference
- 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, but which much more fine-grained control.
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 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
and authorization-level of the issue
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/
:
_10let 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 Account.capabilities.issue()
method to issue a new capability to an object in storage.
The type contained in <>
is the 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 argument to the issue
function is the path to the object in storage that is to be linked to.
When a capability is issued, a capability controller is created for it
in Account.Capabilities
, which allows the
creator of the capability to have fine-grained control over the capability.
Capabilities usually link to objects in the /storage/
domain, but can also be created for Account
objects. Account capabilities will not be covered in this tutorial.
After issuing the capability, it can be stored somewhere or in this case, published
to the account's public section with the account.capabilities.publish()
method.
The caller provides the capability to publish and a public path to publish it at.
To borrow a reference to an object from the capability, we use the capability's borrow
method.
_10let 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 issue
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.
Additionally, the owner of an object can effectively revoke capabilities
they have created by using the delete
method on the Capability Controller
that was created for the capability when it was issued.
Additionally, if the referenced object in storage is moved, capabilities that have been created from that storage path 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_10log(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.
To execute a script, write a function called access(all) 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:
Here's what this script does:
- It gets an
Account
reference withgetAccount
and assigns it to the variablehelloAccount
- Borrows a reference using the
borrow
method for the capability from theCreate Link
transaction and assigns it tohelloReference
- Logs the result of the
hello()
function fromhelloReference
to the console.
_10let helloAccount = getAccount(0x01)
The Account
reference 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.
Then, the script borrows the capability that was created in Create Link
.
_10// Borrow the public capability from the public path of the owner's account_10let helloReference = helloAccount.capabilities_10 .borrow<&HelloWorld.HelloAsset>(/public/HelloAssetTutorial)_10 ?? panic("Could not borrow a reference to the hello capability")
To borrow a capability that is stored in an account, use the account.capabilities.borrow()
function.
borrow()
returns a reference to the storage object that the capability targets.
The borrow will fail if the capability does not exist,
the capabilities target storage path does not store a value,
or the value cannot be borrowed with the given type.
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.
One other really cool feature of scripts is that since they can't actually change anything on chain, they can access any accounts' private storage and objects. This allows scripts even more power to understand the full state of the chain and it is safe because they can't actually make any changes. Also, everything on-chain is publicly readable anyway, so it is a logical feature for a blockchain programming language to have.
A script can get the Account
for an account address using the built-in getAccount()
function:
_10view fun getAuthAccount(_ address: Address): &Account
See the language reference for more information about accounts.
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.