How to invoke functions in other Nexis Native Chain programs.
invoke
or invoke_signed
, the latter being
how programs provide signatures for PDAs that they owninvoke
or invoke_signed
. We’ll be covering both of these later in this
lesson.
invoke
or
invoke_signed
function from the solana_program
crate. You use invoke
to essentially pass
through the original transaction signature that was passed into your program.
You use invoke_signed
to have your program “sign” for its PDAs.
invoke
program_id
- the public key of the program you are going to invokeaccount
- a list of account metadata as a vector. You need to include every
account that the invoked program will read or writedata
- a byte buffer representing the data being passed to the callee
program as a vectorInstruction
type has the following definition:
Instruction
object. Many
individuals and organizations create publicly available crates alongside their
programs that expose these sorts of functions to simplify calling their
programs. This is similar to the Typescript libraries we’ve used in this course
(e.g. @nexis-network/web3.js,
@nexis-network/spl-token).
For example, in this lesson’s lab we’ll be using the spl_token
crate to create
minting instructions. In all other cases, you’ll need to create the
Instruction
instance from scratch.
While the program_id
field is fairly straightforward, the accounts
and
data
fields require some explanation.
Both the accounts
and data
fields are of type Vec
, or vector. You can use
the vec
macro to construct a
vector using array notation, like so:
accounts
field of the Instruction
struct expects a vector of type
AccountMeta
.
The AccountMeta
struct has the following definition:
vec
macro again, which
has an implemented function allowing you to create a vector of certain length.
Once you have initialized an empty vector, you would construct the byte buffer
similar to how you would client-side. Determine the data required by the callee
program and the serialization format used and write your code to match. Feel
free to read up on some of the
features of the vec
macro available to you here.
extend_from_slice
method is probably new to you. It’s a method on vectors that takes a slice as
input, iterates over the slice, clones each element, and then appends it to the
Vec
.
invoke
and invoke_signed
also require a
list of account_info
objects. Just like the list of AccountMeta
objects you
added to the instruction, you must include all of the accounts that the program
you’re calling will read or write.
By the time you make a CPI in your program, you should have already grabbed all
the account_info
objects that were passed into your program and stored them in
variables. You’ll construct your list of account_info
objects for the CPI by
choosing which of these accounts to copy and send along.
You can copy each account_info
object that you need to pass into the CPI using
the
Clone
trait that is implemented on the account_info
struct in the solana_program
crate. This Clone
trait returns a copy of the
account_info
instance.
invoke
invoke
.
invoke
won’t work
if a signature is required on behalf of a PDA. For that, you’ll need to use
invoke_signed
.
invoke_signed
invoke_signed
is a little different just because there is an additional
field that requires the seeds used to derive any PDAs that must sign the
transaction. You may recall from previous lessons that PDAs do not lie on the
Ed25519 curve and, therefore, do not have a corresponding secret key. You’ve
been told that programs can provide signatures for their PDAs, but have not
learned how that actually happens - until now. Programs provide signatures for
their PDAs with the invoke_signed
function. The first two fields of
invoke_signed
are the same as invoke
, but there is an additional
signers_seeds
field that comes into play here.
signers_seeds
field.
The Nexis Native Chain runtime will internally
call create_program_address
using the seeds provided and the program_id
of the calling program. It can
then compare the result against the addresses supplied in the instruction. If
any of the addresses match, then the runtime knows that indeed the program
associated with this address is the caller and thus is authorized to be a
signer.
program_id
, accounts, and data passed into the CPI. Without these security
checks, someone could submit a transaction that invokes an instruction on a
completely different program than was expected, which is not ideal.
Fortunately, there are inherent checks on the validity of any PDAs that are
marked as signers within the invoke_signed
function. All other accounts and
instruction_data
should be verified somewhere in your program code before
making the CPI. It’s also important to make sure you’re targeting the intended
instruction on the program you are invoking. The easiest way to do this is to
read the source code of the program you will be invoking just as you would if
you were constructing an instruction from the client side.
invoke_signed
and
receive this error, then it likely means that the seeds you are providing are
incorrect. You can also find
an example transaction that failed with this error.
Another similar error is thrown when an account that’s written to isn’t marked
as writable
inside the AccountMeta
struct.
MintTo
instruction using a CPI. If you need a refresher on tokens, token mints, and
minting new tokens, have a look at the
Token Program lesson before moving
forward with this lab.
solution-add-comments
branch as our starting point.
Cargo.toml
Cargo.toml
file underneath [dependencies]
. We’ll be using the spl-token
and
spl-associated-token-account
crates in addition to the existing dependencies.
cargo check
in your console to have cargo resolve
your dependencies and ensure that you are ready to continue. Depending on your
setup you may need to modify crate versions before moving on.
add_movie_review
add_movie_review
function. Since we’ll be
minting tokens, the add_movie_review
instruction requires a few new accounts
to be passed in:
token_mint
- the mint address of the tokenmint_auth
- address of the authority of the token mintuser_ata
- user’s associated token account for this mint (where the tokens
will be minted)token_program
- address of the token programinstruction_data
required for the new functionality, so
no changes need to be made to how data is deserialized. The only additional
information that’s needed is the extra accounts.
add_movie_review
LAMPORTS_PER_SOL
at the top of the file.
add_movie_review
function right
before Ok(())
is returned.
Minting tokens requires a signature by the mint authority. Since the program
needs to be able to mint tokens, the mint authority needs to be an account that
the program can sign for. In other words, it needs to be a PDA account owned by
the program.
We’ll also be structuring our token mint such that the mint account is a PDA
account that we can derive deterministically. This way we can always verify that
the token_mint
account passed into the program is the expected account.
Let’s go ahead and derive the token mint and mint authority addresses using the
find_program_address
function with the seeds “token_mint” and “token_auth,”
respectively.
mint_to
function of the token program with
the correct accounts using invoke_signed
. The spl_token
crate provides a
mint_to
helper function for creating the minting instruction. This is great
because it means we don’t have to manually build the entire instruction from
scratch. Rather, we can simply pass in the arguments required by the function.
Here’s the function signature:
token_mint
, user_ata
, and mint_auth
accounts. And, most relevant to this lesson, we provide the seeds used to find
the token_mint
address, including the bump seed.
invoke_signed
and not invoke
here. The Token program
requires the mint_auth
account to sign for this transaction. Since the
mint_auth
account is a PDA, only the program it was derived from can sign on
its behalf. When invoke_signed
is called, the Nexis Native Chain runtime calls
create_program_address
with the seeds and bump provided and then compares the
derived address with all of the addresses of the provided AccountInfo
objects.
If any of the addresses match the derived address, the runtime knows that the
matching account is a PDA of this program and that the program is signing this
transaction for this account.
At this point, the add_movie_review
instruction should be fully functional and
will mint ten tokens to the reviewer when a review is created.
add_comment
add_comment
function will be almost identical to what we
did for the add_movie_review
function above. The only difference is that we’ll
change the amount of tokens minted for a comment from ten to five so that adding
reviews are weighted above commenting. First, update the accounts with the same
four additional accounts as in the add_movie_review
function.
add_comment
function just before the Ok(())
.
Then derive the token mint and mint authority accounts. Remember, both are PDAs
derived from seeds “token_mint” and “token_authority” respectively.
invoke_signed
to send the mint_to
instruction to the Token
program, sending five tokens to the commenter.
MovieInstruction
enum in instruction.rs
.
match
statement in the unpack
function in the same
file under the variant 3
.
process_instruction
function in the processor.rs
file, add the new
instruction to the match
statement and call a function
initialize_token_mint
.
initialize_token_mint
function. This
function will derive the token mint and mint authority PDAs, create the token
mint account, and then initialize the token mint. We won’t explain all of this
in detail, but it’s worth reading through the code, especially given that the
creation and initialization of the token mint both involve CPIs. Again, if you
need a refresher on tokens and mints, have a look at the
Token Program lesson.
cargo build-bpf
and then running the command that is returned, it
should look something like solana program deploy <PATH>
.
Before you can start testing whether or not adding a review or comment sends you
tokens, you need to initialize the program’s token mint. You can use
this script to
do that. Once you’d cloned that repository, replace the PROGRAM_ID
in
index.ts
with your program’s ID. Then run npm install
and then npm start
.
The script assumes you’re deploying to Devnet. If you’re deploying locally, then
make sure to tailor the script accordingly.
Once you’ve initialized your token mint, you can use the
Movie Review frontend
to test adding reviews and comments. Again, the code assumes you’re on Devnet so
please act accordingly.
After submitting a review, you should see 10 new tokens in your wallet! When you
add a comment, you should receive 5 tokens. They won’t have a fancy name or
image since we didn’t add any metadata to the token, but you get the idea.
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.