Invoke other Orbitron Native Chain programs from your Anchor app.
CpiContext
cpi
feature generates CPI helper functions for invoking
instructions on existing Anchor programsinvoke
and invoke_signed
directlyerror_code
attribute macro is used to create custom Anchor ErrorsAnchor makes invoking other Orbitron Native Chain programs easier, especially if the program you’re invoking is also an Anchor program whose crate you can access.
In this lesson, you’ll learn how to construct an Anchor CPI. You’ll also learn how to throw custom errors from an Anchor program so that you can start to write more sophisticated Anchor programs.
CPIs allow programs to invoke instructions on other programs using the invoke
or invoke_signed
functions. This allows new programs to build on top of
existing programs (we call that composability).
While making CPIs directly using invoke
or invoke_signed
is still an option,
Anchor also provides a simplified way to make CPIs by using a CpiContext
.
In this lesson, you’ll use the anchor_spl
crate to make CPIs to the SPL Token
Program. You can
explore what’s available in the anchor_spl
crate.
CpiContext
The first step in making a CPI is to create an instance of CpiContext
.
CpiContext
is very similar to Context
, the first argument type required by
Anchor instruction functions. They are both declared in the same module and
share similar functionality.
The CpiContext
type specifies non-argument inputs for cross program
invocations:
accounts
- the list of accounts required for the instruction being invokedremaining_accounts
- any remaining accountsprogram
- the program ID of the program being invokedsigner_seeds
- if a PDA is signing, include the seeds required to derive the
PDAYou use CpiContext::new
to construct a new instance when passing along the
original transaction signature.
You use CpiContext::new_with_signer
to construct a new instance when signing
on behalf of a PDA for the CPI.
One of the main things about CpiContext
that simplifies cross-program
invocations is that the accounts
argument is a generic type that lets you pass
in any object that adopts the ToAccountMetas
and ToAccountInfos<'info>
traits.
These traits are added by the #[derive(Accounts)]
attribute macro that you’ve
used before when creating structs to represent instruction accounts. That means
you can use similar structs with CpiContext
.
This helps with code organization and type safety.
When the program you’re calling is an Anchor program with a published crate, Anchor can generate instruction builders and CPI helper functions for you.
Simply declare your program’s dependency on the program you’re calling in your
program’s Cargo.toml
file as follows:
By adding features = ["cpi"]
, you enable the cpi
feature and your program
gains access to the callee::cpi
module.
The cpi
module exposes callee
’s instructions as a Rust function that takes
as arguments a CpiContext
and any additional instruction data. These functions
use the same format as the instruction functions in your Anchor programs, only
with CpiContext
instead of Context
. The cpi
module also exposes the
accounts structs required for calling the instructions.
For example, if callee
has the instruction do_something
that requires the
accounts defined in the DoSomething
struct, you could invoke do_something
as
follows:
When the program you’re calling is not an Anchor program, there are two possible options:
anchor_spl
crate provides helper functions that are virtually identical
from a call-site perspective to what you would get with the cpi
module of
an Anchor program. E.g. you can mint using the
mint_to
helper function
and use the
MintTo
accounts struct.
invoke
and invoke_signed
. In fact, the
source code of the mint_to
helper function referenced above shows an
example using invoke_signed
when given a CpiContext
. You can follow a
similar pattern if you decide to use an accounts struct and CpiContext
to
organize and prepare your CPI.
We’re deep enough into Anchor at this point that it’s important to know how to create custom errors.
Ultimately, all programs return the same error
type: ProgramError
.
However, when writing a program using Anchor you can use AnchorError
as an
abstraction on top of ProgramError
. This abstraction provides additional
information when a program fails, including:
Anchor Errors can be divided into:
You can add errors unique to your program by using the error_code
attribute.
Simply add this attribute to a custom enum
type. You can then use the variants
of the enum
as errors in your program. Additionally, you can add an error
message to each variant using the msg
attribute. Clients can then display this
error message if the error occurs.
To return a custom error you can use the err or the error macro from an instruction function. These add file and line information to the error that is then logged by Anchor to help you with debugging.
Alternatively, you can use the require macro to simplify returning errors. The code above can be refactored to the following:
Let’s practice the concepts we’ve gone over in this lesson by building on top of the Movie Review program from previous lessons.
In this lab we’ll update the program to mint tokens to users when they submit a new movie review.
To get started, we will be using the final state of the Anchor Movie Review
program from the previous lesson. So, if you just completed that lesson then
you’re all set and ready to go. If you are just jumping in here, no worries, you
can download the starter code.
We’ll be using the solution-pdas
branch as our starting point.
Cargo.toml
Before we get started we need enable the init-if-needed
feature and add the
anchor-spl
crate to the dependencies in Cargo.toml
. If you need to brush up
on the init-if-needed
feature take a look at the
Anchor PDAs and Accounts lesson.
Next, navigate to lib.rs
and create an instruction to initialize a new token
mint. This will be the token that is minted each time a user leaves a review.
Note that we don’t need to include any custom instruction logic since the
initialization can be handled entirely through Anchor constraints.
Now, implement the InitializeMint
context type and list the accounts and
constraints the instruction requires. Here we initialize a new Mint
account
using a PDA with the string “mint” as a seed. Note that we can use the same PDA
for both the address of the Mint
account and the mint authority. Using a PDA
as the mint authority enables our program to sign for the minting of the tokens.
To initialize the Mint
account, we’ll need to include the token_program
,
rent
, and system_program
in the list of accounts.
There may be some constraints above that you haven’t seen yet. Adding
mint::decimals
and mint::authority
along with init
ensures that the
account is initialized as a new token mint with the appropriate decimals and
mint authority set.
Next, let’s create an Anchor Error that we’ll use when validating the rating
passed to either the add_movie_review
or update_movie_review
instruction.
add_movie_review
instructionNow that we’ve done some setup, let’s update the add_movie_review
instruction
and AddMovieReview
context type to mint tokens to the reviewer.
Next, update the AddMovieReview
context type to add the following accounts:
token_program
- we’ll be using the Token Program to mint tokensmint
- the mint account for the tokens that we’ll mint to users when they
add a movie reviewtoken_account
- the associated token account for the afforementioned mint
and reviewerassociated_token_program
- required because we’ll be using the
associated_token
constraint on the token_account
rent
- required because we are using the init-if-needed
constraint on the
token_account
Again, some of the above constraints may be unfamiliar to you. The
associated_token::mint
and associated_token::authority
constraints along
with the init_if_needed
constraint ensures that if the account has not already
been initialized, it will be initialized as an associated token account for the
specified mint and authority.
Next, let’s update the add_movie_review
instruction to do the following:
rating
is valid. If it is not a valid rating, return the
InvalidRating
error.mint_to
instruction using the mint
authority PDA as a signer. Note that we’ll mint 10 tokens to the user but need
to adjust for the mint decimals by making it 10*10^6
.Fortunately, we can use the anchor_spl
crate to access helper functions and
types like mint_to
and MintTo
for constructing our CPI to the Token Program.
mint_to
takes a CpiContext
and integer as arguments, where the integer
represents the number of tokens to mint. MintTo
can be used for the list of
accounts that the mint instruction needs.
Update your use
statements to include:
Next, update the add_movie_review
function to:
update_movie_review
instructionHere we are only adding the check that rating
is valid.
Those are all of the changes we need to make to the program! Now, let’s update our tests.
Start by making sure your imports and describe
function look like this:
You can run npm install @orbitron-network/spl-token --save-dev
if you don’t have it
installed.
With that done, add a test for the initializeTokenMint
instruction:
Notice that we didn’t have to add .accounts
because they call be inferred,
including the mint
account (assuming you have seed inference enabled).
Next, update the test for the addMovieReview
instruction. The primary
additions are:
After that, neither the test for updateMovieReview
nor the test for
deleteMovieReview
need any changes.
At this point, run anchor test
and you should see the following output
If you need more time with the concepts from this lesson or got stuck along the
way, feel free to take a look at the
solution code.
Note that the solution to this lab is on the solution-add-tokens
branch.
To apply what you’ve learned about CPIs in this lesson, think about how you could incorporate them into the Student Intro program. You could do something similar to what we did in the lab here and add some functionality to mint tokens to users when they introduce themselves.
Try to do this independently if you can! But if you get stuck, feel free to reference this solution code. Note that your code may look slightly different than the solution code depending on your implementation.
Invoke other Orbitron Native Chain programs from your Anchor app.
CpiContext
cpi
feature generates CPI helper functions for invoking
instructions on existing Anchor programsinvoke
and invoke_signed
directlyerror_code
attribute macro is used to create custom Anchor ErrorsAnchor makes invoking other Orbitron Native Chain programs easier, especially if the program you’re invoking is also an Anchor program whose crate you can access.
In this lesson, you’ll learn how to construct an Anchor CPI. You’ll also learn how to throw custom errors from an Anchor program so that you can start to write more sophisticated Anchor programs.
CPIs allow programs to invoke instructions on other programs using the invoke
or invoke_signed
functions. This allows new programs to build on top of
existing programs (we call that composability).
While making CPIs directly using invoke
or invoke_signed
is still an option,
Anchor also provides a simplified way to make CPIs by using a CpiContext
.
In this lesson, you’ll use the anchor_spl
crate to make CPIs to the SPL Token
Program. You can
explore what’s available in the anchor_spl
crate.
CpiContext
The first step in making a CPI is to create an instance of CpiContext
.
CpiContext
is very similar to Context
, the first argument type required by
Anchor instruction functions. They are both declared in the same module and
share similar functionality.
The CpiContext
type specifies non-argument inputs for cross program
invocations:
accounts
- the list of accounts required for the instruction being invokedremaining_accounts
- any remaining accountsprogram
- the program ID of the program being invokedsigner_seeds
- if a PDA is signing, include the seeds required to derive the
PDAYou use CpiContext::new
to construct a new instance when passing along the
original transaction signature.
You use CpiContext::new_with_signer
to construct a new instance when signing
on behalf of a PDA for the CPI.
One of the main things about CpiContext
that simplifies cross-program
invocations is that the accounts
argument is a generic type that lets you pass
in any object that adopts the ToAccountMetas
and ToAccountInfos<'info>
traits.
These traits are added by the #[derive(Accounts)]
attribute macro that you’ve
used before when creating structs to represent instruction accounts. That means
you can use similar structs with CpiContext
.
This helps with code organization and type safety.
When the program you’re calling is an Anchor program with a published crate, Anchor can generate instruction builders and CPI helper functions for you.
Simply declare your program’s dependency on the program you’re calling in your
program’s Cargo.toml
file as follows:
By adding features = ["cpi"]
, you enable the cpi
feature and your program
gains access to the callee::cpi
module.
The cpi
module exposes callee
’s instructions as a Rust function that takes
as arguments a CpiContext
and any additional instruction data. These functions
use the same format as the instruction functions in your Anchor programs, only
with CpiContext
instead of Context
. The cpi
module also exposes the
accounts structs required for calling the instructions.
For example, if callee
has the instruction do_something
that requires the
accounts defined in the DoSomething
struct, you could invoke do_something
as
follows:
When the program you’re calling is not an Anchor program, there are two possible options:
anchor_spl
crate provides helper functions that are virtually identical
from a call-site perspective to what you would get with the cpi
module of
an Anchor program. E.g. you can mint using the
mint_to
helper function
and use the
MintTo
accounts struct.
invoke
and invoke_signed
. In fact, the
source code of the mint_to
helper function referenced above shows an
example using invoke_signed
when given a CpiContext
. You can follow a
similar pattern if you decide to use an accounts struct and CpiContext
to
organize and prepare your CPI.
We’re deep enough into Anchor at this point that it’s important to know how to create custom errors.
Ultimately, all programs return the same error
type: ProgramError
.
However, when writing a program using Anchor you can use AnchorError
as an
abstraction on top of ProgramError
. This abstraction provides additional
information when a program fails, including:
Anchor Errors can be divided into:
You can add errors unique to your program by using the error_code
attribute.
Simply add this attribute to a custom enum
type. You can then use the variants
of the enum
as errors in your program. Additionally, you can add an error
message to each variant using the msg
attribute. Clients can then display this
error message if the error occurs.
To return a custom error you can use the err or the error macro from an instruction function. These add file and line information to the error that is then logged by Anchor to help you with debugging.
Alternatively, you can use the require macro to simplify returning errors. The code above can be refactored to the following:
Let’s practice the concepts we’ve gone over in this lesson by building on top of the Movie Review program from previous lessons.
In this lab we’ll update the program to mint tokens to users when they submit a new movie review.
To get started, we will be using the final state of the Anchor Movie Review
program from the previous lesson. So, if you just completed that lesson then
you’re all set and ready to go. If you are just jumping in here, no worries, you
can download the starter code.
We’ll be using the solution-pdas
branch as our starting point.
Cargo.toml
Before we get started we need enable the init-if-needed
feature and add the
anchor-spl
crate to the dependencies in Cargo.toml
. If you need to brush up
on the init-if-needed
feature take a look at the
Anchor PDAs and Accounts lesson.
Next, navigate to lib.rs
and create an instruction to initialize a new token
mint. This will be the token that is minted each time a user leaves a review.
Note that we don’t need to include any custom instruction logic since the
initialization can be handled entirely through Anchor constraints.
Now, implement the InitializeMint
context type and list the accounts and
constraints the instruction requires. Here we initialize a new Mint
account
using a PDA with the string “mint” as a seed. Note that we can use the same PDA
for both the address of the Mint
account and the mint authority. Using a PDA
as the mint authority enables our program to sign for the minting of the tokens.
To initialize the Mint
account, we’ll need to include the token_program
,
rent
, and system_program
in the list of accounts.
There may be some constraints above that you haven’t seen yet. Adding
mint::decimals
and mint::authority
along with init
ensures that the
account is initialized as a new token mint with the appropriate decimals and
mint authority set.
Next, let’s create an Anchor Error that we’ll use when validating the rating
passed to either the add_movie_review
or update_movie_review
instruction.
add_movie_review
instructionNow that we’ve done some setup, let’s update the add_movie_review
instruction
and AddMovieReview
context type to mint tokens to the reviewer.
Next, update the AddMovieReview
context type to add the following accounts:
token_program
- we’ll be using the Token Program to mint tokensmint
- the mint account for the tokens that we’ll mint to users when they
add a movie reviewtoken_account
- the associated token account for the afforementioned mint
and reviewerassociated_token_program
- required because we’ll be using the
associated_token
constraint on the token_account
rent
- required because we are using the init-if-needed
constraint on the
token_account
Again, some of the above constraints may be unfamiliar to you. The
associated_token::mint
and associated_token::authority
constraints along
with the init_if_needed
constraint ensures that if the account has not already
been initialized, it will be initialized as an associated token account for the
specified mint and authority.
Next, let’s update the add_movie_review
instruction to do the following:
rating
is valid. If it is not a valid rating, return the
InvalidRating
error.mint_to
instruction using the mint
authority PDA as a signer. Note that we’ll mint 10 tokens to the user but need
to adjust for the mint decimals by making it 10*10^6
.Fortunately, we can use the anchor_spl
crate to access helper functions and
types like mint_to
and MintTo
for constructing our CPI to the Token Program.
mint_to
takes a CpiContext
and integer as arguments, where the integer
represents the number of tokens to mint. MintTo
can be used for the list of
accounts that the mint instruction needs.
Update your use
statements to include:
Next, update the add_movie_review
function to:
update_movie_review
instructionHere we are only adding the check that rating
is valid.
Those are all of the changes we need to make to the program! Now, let’s update our tests.
Start by making sure your imports and describe
function look like this:
You can run npm install @orbitron-network/spl-token --save-dev
if you don’t have it
installed.
With that done, add a test for the initializeTokenMint
instruction:
Notice that we didn’t have to add .accounts
because they call be inferred,
including the mint
account (assuming you have seed inference enabled).
Next, update the test for the addMovieReview
instruction. The primary
additions are:
After that, neither the test for updateMovieReview
nor the test for
deleteMovieReview
need any changes.
At this point, run anchor test
and you should see the following output
If you need more time with the concepts from this lesson or got stuck along the
way, feel free to take a look at the
solution code.
Note that the solution to this lab is on the solution-add-tokens
branch.
To apply what you’ve learned about CPIs in this lesson, think about how you could incorporate them into the Student Intro program. You could do something similar to what we did in the lab here and add some functionality to mint tokens to users when they introduce themselves.
Try to do this independently if you can! But if you get stuck, feel free to reference this solution code. Note that your code may look slightly different than the solution code depending on your implementation.