How to safely invoke Nexis Native Chain programs from other Nexis Native Chain programs.
cpi
instruction invokes the
transfer
instruction on token_program
, but there is no code that checks
whether or not the token_program
account passed into the instruction is, in
fact, the SPL Token Program.
cpi
instruction to check whether or not token_program
’s public key is that of the
SPL Token Program.
ProgramError::IncorrectProgramId
error.
Depending on the program you’re invoking with your CPI, you can either hard code
the address of the expected program ID or use the program’s Rust crate to get
the address of the program, if available. In the example above, the spl_token
crate provides the address of the SPL Token Program.
declare_id()
macro to define the address of the
program. When a CPI module is generated for a specific program, it uses the
address passed into this macro as the “source of truth” and will automatically
verify that all CPIs made using its CPI module target this program id.
While at the core no different than manual program checks, using CPI modules
avoids the possibility of forgetting to perform a program check or accidentally
typing in the wrong program ID when hard-coding it.
The program below shows an example of using a CPI module for the SPL Token
Program to perform the transfer shown in the previous examples.
Program
account type
to validate the passed-in program in your account validation struct. Between
the anchor_lang
and anchor_spl
crates,
the following Program
types are provided out of the box:
If you have access to an Anchor program’s CPI module, you typically can import
its program type with the following, replacing the program name with the name of
the actual program:
starter
branch of
this repository.
Clone the repository and then open it on the starter
branch.
Notice that there are three programs:
gameplay
character-metadata
fake-metadata
tests
directory.
The first program, gameplay
, is the one that our test directly uses. Take a
look at the program. It has two instructions:
create_character_insecure
- creates a new character and CPI’s into the
metadata program to set up the character’s initial attributesbattle_insecure
- pits two characters against each other, assigning a “win”
to the character with the highest attributescharacter-metadata
, is meant to be the “approved” program
for handling character metadata. Have a look at this program. It has a single
instruction for create_metadata
that creates a new PDA and assigns a
pseudo-random value between 0 and 20 for the character’s health and power.
The last program, fake-metadata
is a “fake” metadata program meant to
illustrate what an attacker might make to exploit our gameplay
program. This
program is almost identical to the character-metadata
program, only it assigns
a character’s initial health and power to be the max allowed: 255.
create_character_insecure
instructiontests
directory for this. It’s long, but take a
minute to look at it before we talk through it together:
create_character_insecure
instruction has no program checks, it still
executes.
The result is that the regular character has the appropriate amount of health
and power: each a value between 0 and 20. But the attacker’s health and power
are each 255, making the attacker unbeatable.
If you haven’t already, run anchor test
to see that this test in fact behaves
as described.
create_character_secure
instructioncharacter-metadata
program’s cpi
crate to do the CPI rather than just using
invoke
.
If you want to test out your skills, try this on your own before moving ahead.
We’ll start by updating our use
statement at the top of the gameplay
programs lib.rs
file. We’re giving ourselves access to the program’s type for
account validation, and the helper function for issuing the create_metadata
CPI.
CreateCharacterSecure
. This time, we make metadata_program
a Program
type:
create_character_secure
instruction. It will be the same as
before but will use the full functionality of Anchor CPIs rather than using
invoke
directly:
create_character_secure
anchor test
if you haven’t already. Notice that an error was thrown as
expected, detailing that the program ID passed into the instruction is not the
expected program ID:
solution
branch of
the same repository.