Instructions & CPIs
Instructions are the building blocks of Solana programs, defining the actions that can be performed. In Anchor, instructions are implemented as functions with specific attributes and constraints. Let's explore how to work with them effectively.
Instruction Structure
In Anchor, instructions are defined using the #[program]
module and individual instruction functions. Here's the basic structure:
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
// Instruction logic here
Ok(())
}
}
Instruction Context
Every instruction function receives a Context
struct as its first parameter. This context contains:
accounts
: The accounts passed to the instructionprogram_id
: The program's public keyremaining_accounts
: Any additional accounts not explicitly defined in the context structbumps
: Thebumps
field is particularly useful when working with PDAs, as it provides the bump seeds that were used to derive the PDA addresses (only if you're deriving them in the account struct)
That can be accessed by doing:
// Accessing accounts
ctx.accounts.account_1
ctx.accounts.account_2
// Accessing program ID
ctx.program_id
// Accessing remaining accounts
for remaining_account in ctx.remaining_accounts {
// Process remaining account
}
// Accessing bumps for PDAs
let bump = ctx.bumps.pda_account;
Instruction Discriminator
Like accounts, instructions in Anchor use discriminators to identify different instruction types. The default discriminator is an 8-byte prefix generated using sha256("global:<instruction_name>")[0..8]
. The instruction name should be in snake_case.
Custom Instruction Discriminator
You can also specify a custom discriminator for your instructions:
#[instruction(discriminator = 1)]
pub fn custom_discriminator(ctx: Context<Custom>) -> Result<()> {
// Instruction logic
Ok(())
}
Instruction Scaffold
You can write your instruction in different way, in this section we're going to teach some of the style and way you can actually set them up
Instruction Logic
Instruction logic can be organized in different ways, depending on your program's complexity and your preferred coding style. Here are the main approaches:
- Inline Instruction Logic
For simple instructions, you can write the logic directly in the instruction function:
pub fn initialize(ctx: Context<Transfer>, amount: u64) -> Result<()> {
// Transfer tokens logic
// Close token account logic
Ok(())
}
- Separate Module Implementation
For very complex programs, you can organize the logic in separate modules:
// In a separate file: transfer.rs
pub fn execute(ctx: Context<Transfer>, amount: u64) -> Result<()> {
// Transfer tokens logic
// Close token account logic
Ok(())
}
// In your lib.rs
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
transfer::execute(ctx, amount)
}
- Separate Context Implementation
For more complex instructions, you can move the logic to the context struct's implementation:
// In a separate file: transfer.rs
pub fn handler(ctx: Context<Transfer>, amount: u64) -> Result<()> {
ctx.accounts.transfer_tokens(amount)?;
ctx.accounts.close_token_account()?;
Ok(())
}
impl<'info> Transfer<'info> {
/// Transfers tokens from source to destination account
pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
// Transfer tokens logic
Ok(())
}
/// Closes the source token account after transfer
pub fn close_token_account(&mut self) -> Result<()> {
// Close token account logic
}
}
// In your lib.rs
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
transfer::execute(ctx, amount)
}
Instruction Parameters
Instructions can accept parameters beyond the context. These parameters are serialized and deserialized automatically by Anchor. Here are the key points about instruction parameters:
- Basic Types
Anchor supports all Rust primitive types and common Solana types:
pub fn complex_instruction(
ctx: Context<Complex>,
amount: u64,
pubkey: Pubkey,
vec_data: Vec<u8>,
) -> Result<()> {
// Instruction logic
Ok(())
}
- Custom Types
You can use custom types as parameters, but they must implement AnchorSerialize
and AnchorDeserialize
:
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct InstructionData {
pub field1: u64,
pub field2: String,
}
pub fn custom_type_instruction(
ctx: Context<Custom>,
data: InstructionData,
) -> Result<()> {
// Instruction logic
Ok(())
}
Best Practices
-
Keep Instructions Focused: Each instruction should do one thing well. If an instruction is doing too much, consider splitting it into multiple instructions.
-
Use Context Implementation: For complex instructions, use the context implementation approach to:
- Keep your code organized
- Make it easier to test
- Improve reusability
- Add proper documentation
-
Error Handling: Always use proper error handling and return meaningful error messages:
#[error_code]
pub enum TransferError {
#[msg("Insufficient balance")]
InsufficientBalance,
#[msg("Invalid amount")]
InvalidAmount,
}
impl<'info> Transfer<'info> {
pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
require!(amount > 0, TransferError::InvalidAmount);
require!(
self.source.amount >= amount,
TransferError::InsufficientBalance
);
// Transfer logic
Ok(())
}
}
- Documentation: Always document your instruction logic, especially when using context implementation:
impl<'info> Transfer<'info> {
/// # Transfers tokens
///
/// Transfers tokens from source to destination account
pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
// Implementation
Ok(())
}
}
Cross-Program Invocations (CPIs)
Cross Program Invocations (CPI) refer to the process of one program invoking instructions of another program, which enables the composability of Solana programs. Anchor provides a convenient way to make CPIs through the CpiContext
and program-specific builders.
Note: You can find all the System Program CPI by using the main anchor crate and doing: use anchor_lang::system_program::*
; and for the one relative to the SPL token program we'll need to import the anchor_spl crate and do: use anchor_spl::token::*
Basic CPI Structure
Here's how to make a basic CPI:
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::system_program::{transfer, Transfer};
pub fn transfer_lamport(ctx: Context<TransferLamport>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
transfer(cpi_ctx, amount)?;
Ok(())
}
CPI with PDA Signers
When making CPIs that require PDA signatures, use CpiContext::new_with_signer
:
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::system_program::{transfer, Transfer};
pub fn transfer_lamport_with_pda(ctx: Context<TransferLamportWithPda>) -> Result<()> {
let seeds = &[
b"vault".as_ref(),
&[ctx.bumps.vault],
];
let signer = &[&seeds[..]];
let cpi_accounts = Transfer {
from: ctx.accounts.vault.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
transfer(cpi_ctx, amount)?;
Ok(())
}
Error Handling
Anchor provides a robust error handling system for instructions. Here's how to implement custom errors and handle them in your instructions:
#[error_code]
pub enum MyError {
#[msg("Custom error message")]
CustomError,
#[msg("Another error with value: {0}")]
ValueError(u64),
}
pub fn handle_errors(ctx: Context<HandleErrors>, value: u64) -> Result<()> {
require!(value > 0, MyError::CustomError);
require!(value < 100, MyError::ValueError(value));
Ok(())
}