Anchor
Anchor para Iniciantes

Anchor para Iniciantes

Advanced Anchor

Às vezes, a abstração feita com o Anchor torna impossível construir a lógica que nosso program precisa. Por essa razão, nesta seção vamos falar sobre como usar alguns conceitos avançados para trabalhar com nosso program.

Feature Flags

Engenheiros de software rotineiramente precisam de ambientes distintos para desenvolvimento local, testes e produção. Feature flags fornecem uma solução elegante ao habilitar compilação condicional e configurações específicas por ambiente sem manter bases de código separadas.

Cargo Features

As Cargo features oferecem um mecanismo poderoso para compilação condicional e dependências opcionais. Você define features nomeadas na tabela [features] do seu Cargo.toml e depois as habilita ou desabilita conforme necessário:

  • Habilite features via linha de comando: --features feature_name

  • Habilite features para dependências diretamente no Cargo.toml

Isso lhe dá controle granular sobre o que é incluído no seu binário final.

Feature Flags no Anchor

Programas Anchor comumente usam feature flags para criar comportamentos, restrições ou configurações diferentes com base no ambiente alvo. Use o atributo cfg para compilar código condicionalmente:

rust
#[cfg(feature = "testing")]
fn function_for_testing() {
   // Compiled only when "testing" feature is enabled
}

#[cfg(not(feature = "testing"))]
fn function_for_production() {
   // Compiled only when "testing" feature is disabled
}

Feature flags são excelentes para lidar com diferenças entre ambientes. Como nem todos os tokens são implantados na Mainnet e Devnet, você frequentemente precisa de endereços diferentes para redes diferentes:

rust
#[cfg(feature = "localnet")]
pub const TOKEN_ADDRESS: &str = "Local_Token_Address_Here";

#[cfg(not(feature = "localnet"))]
pub const TOKEN_ADDRESS: &str = "Mainnet_Token_Address_Here";

Essa abordagem elimina erros de configuração de implantação e simplifica seu fluxo de trabalho de desenvolvimento ao trocar de ambiente em tempo de compilação em vez de tempo de execução.

É assim que você configuraria no seu arquivo Cargo.toml:

toml
[features]
default = ["localnet"]
localnet = []

Depois disso, você pode especificar a flag com a qual deseja compilar seu program assim:

text
# Uses default (localnet)
anchor build

# Build for mainnet
anchor build --no-default-features

Uma vez que você compila o program, o binário terá apenas a flag condicional que você habilitou, o que significa que ao testar e implantar, ele respeitará essa condição.

Working with Raw Accounts

O Anchor simplifica o manuseio de accounts ao desserializar automaticamente as accounts através do contexto de account:

rust
#[derive(Accounts)]
pub struct Instruction<'info> {
    pub account: Account<'info, MyAccount>,
}

#[account]
pub struct MyAccount {
    pub data: u8,
}

No entanto, essa desserialização automática torna-se problemática quando você precisa de processamento condicional de accounts, como desserializar e modificar uma account apenas quando critérios específicos são atendidos.

Para cenários condicionais, use UncheckedAccount para adiar a validação e desserialização até o tempo de execução. Isso previne erros fatais quando accounts podem não existir ou quando você precisa validá-las programaticamente:

rust
#[derive(Accounts)]
pub struct Instruction<'info> {
    /// CHECK: Validated conditionally in instruction logic
    pub account: UncheckedAccount<'info>,
}

#[account]
pub struct MyAccount {
    pub data: u8,
}

pub fn instruction(ctx: Context<Instruction>, should_process: bool) -> Result<()> {
    if should_process {
        // Deserialize the account data
        let mut account = MyAccount::try_deserialize(&mut &ctx.accounts.account.to_account_info().data.borrow_mut()[..])
            .map_err(|_| error!(InstructionError::AccountNotFound))?;

        // Modify the account data
        account.data += 1;

        // Serialize back the data to the account
        account.try_serialize(&mut &mut ctx.accounts.account.to_account_info().data.borrow_mut()[..])?;
    }

    Ok(())
}

Zero Copy Accounts

O runtime da Solana impõe limites rígidos de memória: 4KB para memória de stack e 32KB para memória de heap. Além disso, a stack cresce 10KB para cada account carregada. Essas restrições tornam a desserialização tradicional impossível para accounts grandes, exigindo técnicas de zero-copy para gerenciamento eficiente de memória.

Quando as accounts excedem esses limites, você encontrará erros de stack overflow como: Stack offset of -30728 exceeded max offset of -4096 by 26632 bytes

Para accounts de tamanho médio, você pode usar Box para mover dados da stack para o heap (como mencionado na introdução), mas accounts maiores exigem a implementação de zero_copy.

Zero-copy ignora completamente a desserialização automática usando acesso direto à memória bruta. Para definir um tipo de account que usa zero-copy, anote a struct com #[account(zero_copy)]:

rust
#[account(zero_copy)]
pub struct Data {
    // 10240 bytes - 8 bytes account discriminator
    pub data: [u8; 10_232],
}

O atributo #[account(zero_copy)] implementa automaticamente vários traits necessários para desserialização zero-copy:

  • #[derive(Copy, Clone)],

  • #[derive(bytemuck::Zeroable)],

  • #[derive(bytemuck::Pod)], e

  • #[repr(C)]

Nota: Para usar zero-copy, adicione o crate bytemuck às suas dependências com a feature min_const_generics para trabalhar com arrays de qualquer tamanho nos seus tipos zero-copy.

Para desserializar uma account zero-copy no contexto da sua instruction, use AccountLoader<'info, T>, onde T é o seu tipo de account zero-copy:

rust
#[derive(Accounts)]
pub struct Instruction<'info> {
    pub data_account: AccountLoader<'info, Data>,
}

Inicializando uma Account Zero Copy

Existem duas abordagens diferentes para inicialização, dependendo do tamanho da sua account:

Para accounts abaixo de 10.240 bytes, use a restrição init diretamente:

rust
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        // 10240 bytes is max space to allocate with init constraint
        space = 8 + 10_232,
        payer = payer,
    )]
    pub data_account: AccountLoader<'info, Data>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

A restrição init é limitada a 10.240 bytes devido a limitações de CPI. Por baixo dos panos, init faz uma chamada CPI ao System Program para criar a account.

Para accounts que precisam de mais de 10.240 bytes, você deve primeiro criar a account separadamente chamando o System Program múltiplas vezes, adicionando 10.240 bytes por transação. Isso permite criar accounts até o tamanho máximo da Solana de 10MB (10.485.760 bytes), contornando a limitação de CPI.

Após criar a account externamente, use a restrição zero em vez de init. A restrição zero verifica se a account não foi inicializada checando se seu discriminator não está definido:

rust
#[account(zero_copy)]
pub struct Data {
    // 10,485,780 bytes - 8 bytes account discriminator
    pub data: [u8; 10_485_752],
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(zero)]
    pub data_account: AccountLoader<'info, Data>,
}

Para ambos os métodos de inicialização, chame load_init() para obter uma referência mutável aos dados da account e definir o discriminator da account:

rust
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    let account = &mut ctx.accounts.data_account.load_init()?;

    // Set your account data here
    // account.data = something;

    Ok(())
}

Carregando uma Account Zero Copy

Uma vez inicializada, use load() para ler os dados da account:

rust
#[derive(Accounts)]
pub struct ReadOnly<'info> {
    pub data_account: AccountLoader<'info, Data>,
}

pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {
    let account = &ctx.accounts.data_account.load()?;

    // Read your data here
    // let value = account.data;
    
    Ok(())
}

Working with Raw CPIs

O Anchor abstrai a complexidade de cross-program invocation (CPI), mas entender a mecânica subjacente é crucial para o desenvolvimento avançado na Solana.

Toda instruction consiste em três componentes principais: um program_id, um array de accounts e bytes de instruction_data que o Solana Runtime processa via syscall sol_invoke.

No nível do sistema, a Solana executa CPIs através desta syscall:

rust
/// Solana BPF syscall for invoking a signed instruction.
fn sol_invoke_signed_c(
    instruction_addr: *const u8,
    account_infos_addr: *const u8,
    account_infos_len: u64,
    signers_seeds_addr: *const u8,
    signers_seeds_len: u64,
) -> u64;

O Runtime recebe ponteiros para seus dados de instruction e informações de account, e então executa o program alvo com essas entradas.

Veja como você invocaria um programa Anchor usando primitivas brutas da Solana:

rust
pub fn manual_cpi(ctx: Context<MyCpiContext>) -> Result<()> {
    // Construct instruction discriminator (8-byte SHA256 hash prefix)
    let discriminator = sha256("global:instruction_name")[0..8]
    
    // Build complete instruction data
    let mut instruction_data = discriminator.to_vec();
    instruction_data.extend_from_slice(&[additional_instruction_data]); // Your instruction parameters
    
    // Define account metadata for the target program
    let accounts = vec![
        AccountMeta::new(ctx.accounts.account_1.key(), true),           // Signer + writable
        AccountMeta::new_readonly(ctx.accounts.account_2.key(), false), // Read-only
        AccountMeta::new(ctx.accounts.account_3.key(), false),          // Writable
    ];
    
    // Collect account infos for the syscall
    let account_infos = vec![
        ctx.accounts.account_1.to_account_info(),
        ctx.accounts.account_2.to_account_info(),
        ctx.accounts.account_3.to_account_info(),
    ];
    
    // Create the instruction
    let instruction = solana_program::instruction::Instruction {
        program_id: target_program::ID,
        accounts,
        data: instruction_data,
    };
    
    // Execute the CPI
    solana_program::program::invoke(&instruction, &account_infos)?;
    
    Ok(())
}

// For PDA-signed CPIs, use invoke_signed instead:
pub fn pda_signed_cpi(ctx: Context<PdaCpiContext>) -> Result<()> {
    // ... instruction construction same as above ...
    
    let signer_seeds = &[
        b"seed",
        &[bump],
    ];
    
    solana_program::program::invoke_signed(
        &instruction,
        &account_infos,
        &[signer_seeds],
    )?;
    
    Ok(())
}

CPI para outro Anchor Program

A macro declare_program!() do Anchor habilita invocações cross-program com segurança de tipos sem adicionar o program alvo como dependência. A macro gera módulos Rust a partir do IDL do program, fornecendo helpers de CPI e tipos de account para interações perfeitas entre programas.

Coloque o arquivo IDL do program alvo em um diretório /idls em qualquer lugar da estrutura do seu projeto:

text
project/
├── idls/
│   └── target_program.json
├── programs/
│   └── your_program/
└── Cargo.toml

Então use a macro para gerar os módulos necessários:

rust
use anchor_lang::prelude::*;

declare_id!("YourProgramID");

// Generate modules from IDL
declare_program!(target_program);

// Import the generated types
use target_program::{
    accounts::Counter,             // Account types
    cpi::{self, accounts::*},      // CPI functions and account structs  
    program::TargetProgram,        // Program type for validation
};

#[program]
pub mod your_program {
    use super::*;

    pub fn call_other_program(ctx: Context<CallOtherProgram>) -> Result<()> {
        // Create CPI context
        let cpi_ctx = CpiContext::new(
            ctx.accounts.target_program.to_account_info(),
            Initialize {
                payer: ctx.accounts.payer.to_account_info(),
                counter: ctx.accounts.counter.to_account_info(),
                system_program: ctx.accounts.system_program.to_account_info(),
            },
        );

        // Execute the CPI using generated helper
        target_program::cpi::initialize(cpi_ctx)?;
        
        Ok(())
    }
}

#[derive(Accounts)]
pub struct CallOtherProgram<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(mut)]
    pub counter: Account<'info, Counter>,  // Uses generated account type    
    pub target_program: Program<'info, TargetProgram>,
    pub system_program: Program<'info, System>,
}

Você pode fazer CPI para outro programa Anchor adicionando no seu Cargo.toml sob [dependencies]: callee = { path = "../callee", features = ["cpi"] } após compilar seu program fazendo anchor build -- --features cpi e usando callee::cpi::<instruction>(). Isso não é recomendado pois pode causar um erro de dependência circular. `

Blueshift © 2026Commit: 1b88646