Este guia de início rápido usa o projeto inicial do
framework Anchor gerado por anchor init.
Você criará o projeto localmente, executará seus testes, compilará o programa e
percorrerá o código que o Anchor gera.
Pré-requisitos
Antes de começar, instale as ferramentas de desenvolvimento Solana. A instalação inclui Rust, a CLI do Solana e a CLI do Anchor.
Use a versão 1.1.2 ou superior da CLI do Anchor para este template. Verifique sua versão instalada:
$anchor --version
Criar o projeto
Execute os seguintes comandos no seu terminal:
$anchor init my-program$cd my-program
O projeto inicial inclui um programa Solana em programs/my-program. O programa
inclui duas instruções: uma para inicializar uma conta contadora e outra para
incrementar o contador.
Algumas partes do template demonstram padrões comuns de programas Solana: derivação de endereços de conta PDA, realização de um Cross Program Invocation (CPI) para transferir SOL, e uso de verificações de erro personalizadas para interromper uma instrução quando uma condição falha.
Compilar o Programa
Execute anchor build para compilar o programa inicial:
$anchor build
O programa compilado é gravado em target/deploy/my_program.so. Quando o
programa é implantado, o conteúdo deste arquivo .so é armazenado em uma conta
na blockchain.
Executar Teste
Execute o teste padrão:
$anchor test
O Anchor.toml deste template usa o comando de teste do Rust:
skip_local_validator = true[scripts]test = "cargo test"
O teste carrega o programa compilado no LiteSVM,
cria um pagador, envia as instruções initialize e increment, em
seguida verifica o estado da conta do contador.
Executar anchor test também compila o programa, portanto não é necessário
executar anchor build primeiro ao testar localmente.
Implantar Programa
Os testes locais são o ciclo de feedback mais rápido. Quando estiver pronto para implantar em uma rede, por exemplo devnet, primeiro faça o build e depois implante em um cluster.
Implantar um programa Solana requer SOL porque o programa é armazenado em um program account, e o program account deve pagar pelo espaço que utiliza. Na devnet, solicite SOL gratuito da devnet no Solana Faucet ou com a Solana CLI:
$solana airdrop 2 --url devnet
$anchor build$anchor deploy --provider.cluster devnet
Arquivos de origem
O diretório src contém o programa Solana. A documentação de
Estrutura de Programa
do Anchor explica as macros principais usadas aqui, incluindo declare_id!,
#[program], #[derive(Accounts)] e #[account]. Esta seção
percorre os arquivos do template.
lib.rs
lib.rs é o ponto de entrada do programa. Ele conecta os arquivos de origem,
define o endereço do programa e define as instruções do programa que os usuários
podem chamar.
pub mod constants;pub mod error;pub mod instructions;pub mod state;use anchor_lang::prelude::*;pub use constants::*;pub use instructions::*;pub use state::*;declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");#[program]pub mod my_program {use super::*;pub fn initialize(ctx: Context<Initialize>) -> Result<()> {crate::instructions::initialize::handle_initialize(ctx)}pub fn increment(ctx: Context<Increment>) -> Result<()> {crate::instructions::increment::handle_increment(ctx)}}
[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"
declare_id!("82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd");
O mesmo endereço de programa aparece na configuração e no código. Anchor.toml
informa ao Anchor qual endereço implantar ou chamar para um cluster.
declare_id! define o endereço do programa para verificações de segurança.
constants.rs
constants.rs mantém valores compartilhados em um único lugar. Neste template,
COUNTER_SEED deriva o PDA do contador, HELLO_WORLD_LAMPORTS é
transferido durante a inicialização, e MAX_COUNT é verificado antes de
incrementar.
use anchor_lang::prelude::*;#[constant]pub const COUNTER_SEED: &[u8] = b"counter";#[constant]pub const HELLO_WORLD_LAMPORTS: u64 = 1;#[constant]pub const MAX_COUNT: u64 = 10;
#[derive(Accounts)]pub struct Initialize<'info> {// ...#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {// ...anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;Ok(())}
initialize.rs usa duas constantes:
COUNTER_SEEDderiva o endereço PDA do contador.HELLO_WORLD_LAMPORTSdefine o valor transferido do pagador para a conta do contador.
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;Ok(())}
increment.rs usa MAX_COUNT como limite superior do
contador. Se a contagem atual já estiver no máximo, require! retorna
CounterOverflow e os dados da conta não são alterados.
state.rs
state.rs define tipos de dados personalizados para as contas que o programa
cria e gerencia. O programa define instruções para criar, inicializar e
atualizar esses dados, mas os dados do contador não são armazenados dentro do
próprio programa. Eles são armazenados em uma conta separada com seu próprio
endereço.
use anchor_lang::prelude::*;#[account]#[derive(InitSpace)]pub struct Counter {pub count: u64,pub authority: Pubkey,}
#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,// ...pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();Ok(())}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {// ...ctx.accounts.counter.count += 1;Ok(())}
state.rs define os dados da conta Counter. Os arquivos de instrução usam
esse tipo quando criam e atualizam a conta:
Counter::INIT_SPACEdimensiona a conta para os campos definidos emstate.rs.counteauthoritysão os valores dos campos gravados quando a conta é inicializada.count += 1atualiza o valor do contador armazenado após a validação ser concluída.
error.rs
error.rs define os erros personalizados do programa. Neste template, os erros
demonstram como os handlers de instrução são interrompidos quando um chamador
não tem permissão para atualizar o contador ou quando o contador já atingiu
MAX_COUNT.
use anchor_lang::prelude::*;#[error_code]pub enum ErrorCode {#[msg("Only the counter authority can update this counter")]Unauthorized,#[msg("Counter has reached the maximum value")]CounterOverflow,}
pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
error.rs nomeia os erros que a instrução pode retornar:
ErrorCode::Unauthorizedé retornado quando o assinante não é a autoridade armazenada na conta do contador.ErrorCode::CounterOverflowé retornado quando o contador já atingiuMAX_COUNT.
instructions.rs
instructions.rs conecta os arquivos de instrução ao crate do programa para que
lib.rs possa acessar o código de instrução initialize e increment.
Cada arquivo de instrução define as contas exigidas por essa instrução e a
lógica do handler que é executada após o Anchor validar essas contas.
pub mod initialize;pub mod increment;pub use initialize::*;pub use increment::*;
initialize.rs
initialize.rs define as contas necessárias para criar a conta do contador e,
em seguida, grava os primeiros valores da conta. A struct
#[derive(Accounts)] utiliza as
restrições de conta
do Anchor para especificar quais contas são necessárias e como a nova conta do
contador é criada.
use anchor_lang::prelude::*;use crate::{constants::*, state::Counter};#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Account Context
A struct Initialize define as contas que devem ser incluídas quando um
usuário chama a instrução initialize. O Anchor verifica essas contas antes
de o handler ser executado.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Payer Account
A conta payer paga pela criação da conta do contador. O tipo
Signer<'info> significa que o pagador deve assinar a transação, e
#[account(mut)] indica que a conta do pagador pode ser alterada, pois
lamports serão deduzidos.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Counter Account
A conta counter armazena os dados Counter de state.rs. init
instrui o Anchor a criar essa conta antes de o handler ser executado, e
payer = payer informa ao Anchor qual conta paga pela criação.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Tamanho da Conta
A restrição space informa ao Anchor quanto espaço de dados da conta deve
ser alocado. O Anchor armazena primeiro um discriminador de 8 bytes, seguido dos
bytes necessários para os campos Counter. O discriminador permite que o
Anchor reconheça esta conta como uma conta Counter antes de desserializar
os dados da conta.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Endereço do Contador
As restrições seeds e bump definem o endereço PDA esperado para a
conta do contador. O Anchor verifica se a conta counter fornecida
corresponde a esse endereço. O template usa um PDA para que os utilizadores
possam derivar o endereço do contador a partir do ID do programa e do seed,
tornando o endereço do contador determinístico.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
System Program
A conta system_program é necessária porque a criação de uma nova conta
utiliza o System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
Função Handler
A função handle_initialize é executada depois que o Anchor valida as
contas em Initialize. O valor ctx fornece ao handler acesso a essas
contas verificadas.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Dados Iniciais
O handler grava os primeiros valores na nova conta do contador. A contagem
começa em 0, e o pagador torna-se a autoridade autorizada a incrementar o
contador posteriormente.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Contas de Transferência
Esta CPI de transferência está incluída apenas para demonstrar como uma CPI
passa contas para outro programa. A estrutura Transfer lista as contas
utilizadas pela transferência do System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Contexto CPI
CpiContext::new combina o programa sendo chamado com as contas passadas
para esse programa. Esta é a forma básica de um CPI: escolher o programa a
invocar, coletar as contas que esse programa espera, depois passar ambos para a
invocação. Aqui, o programa sendo chamado é o System Program.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Invocar Transferência
anchor_lang::system_program::transfer invoca a instrução de transferência
do System Program. Neste modelo, a transferência é um pequeno exemplo de como
chamar outro programa a partir do seu programa. Se o CPI de transferência
falhar, a instrução initialize também falhará.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
Registrar Mensagem
msg! escreve uma mensagem nos logs do programa. Ok(()) indica que a
instrução foi concluída com sucesso.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {ctx.accounts.counter.count = 0;ctx.accounts.counter.authority = ctx.accounts.payer.key();let cpi_accounts = anchor_lang::system_program::Transfer {from: ctx.accounts.payer.to_account_info(),to: ctx.accounts.counter.to_account_info(),};let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;msg!("Hello, world! Counter initialized");Ok(())}
increment.rs
increment.rs define as contas necessárias para atualizar uma conta de contador
existente. O handler verifica se o signatário é a autoridade armazenada,
verifica se a contagem não atingiu o MAX_COUNT especificado e, em seguida,
incrementa a contagem.
use anchor_lang::prelude::*;use crate::{constants::*, error::ErrorCode, state::Counter};#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Contexto da Conta
A struct Increment define as contas que devem ser incluídas quando um
usuário chama a instrução increment. O Anchor verifica essas contas antes
de o handler ser executado.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Conta do Contador
A conta counter armazena os dados de Counter. A restrição mut
permite que o handler atualize a contagem armazenada, e as restrições
seeds e bump verificam o endereço PDA do contador.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Signatário com Autoridade
A conta authority deve assinar a transação. O handler verifica
posteriormente se este signatário corresponde à autoridade armazenada na conta
do contador.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
Função Handler
A função handle_increment é executada após o Anchor validar as contas em
Increment. O valor ctx dá ao handler acesso a essas contas
verificadas.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Verificação de Autoridade
A primeira verificação garante que o signatário tem permissão para atualizar
este contador. Se o endereço do signatário não corresponder a
counter.authority, a instrução é interrompida com
ErrorCode::Unauthorized. Isso demonstra autorização em nível de aplicação:
o programa é proprietário dos dados do contador, mas implementa uma regra que
define qual signatário pode alterar esses dados.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Verificação de Contagem Máxima
A segunda verificação impede que o contador ultrapasse MAX_COUNT. Se o
contador já atingiu o limite, a instrução é interrompida com
ErrorCode::CounterOverflow. Este limite é uma regra artificial no template
para que você possa ver como erros personalizados interrompem uma instrução
antes que os dados da conta sejam alterados.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Atualizar Contagem
Somente após ambas as verificações passarem é que o handler atualiza os dados da conta. Esta linha adiciona um ao valor do contador armazenado.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Mensagem de Log
msg! registra a contagem atualizada nos logs do programa. Ok(())
indica que a instrução foi executada com sucesso.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {require_keys_eq!(ctx.accounts.counter.authority,ctx.accounts.authority.key(),ErrorCode::Unauthorized,);require!(ctx.accounts.counter.count < MAX_COUNT,ErrorCode::CounterOverflow,);ctx.accounts.counter.count += 1;msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);Ok(())}
Arquivo de teste
programs/my-program/tests/test_initialize.rs é um teste de integração em Rust.
Ele não inicia um validator local. Em vez disso, carrega o arquivo .so
compilado no LiteSVM, constrói transações que chamam o programa e lê a conta do
contador após cada transação. O teste constrói instruções para uma transação
Solana especificando o ID do programa a ser invocado, fornecendo instruction
data e passando as contas necessárias.
#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub payer: Signer<'info>,#[account(init,payer = payer,space = 8 + Counter::INIT_SPACE,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub system_program: Program<'info, System>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);
O contexto de conta Initialize define as contas necessárias pela instrução
initialize. O teste passa essas mesmas contas para o helper gerado
my_program::accounts::Initialize:
payeré passado comopayer: payer.pubkey().counteré passado comocounter.system_programé passado comosystem_program::ID.
my_program::instruction::Initialize {}.data()
cria o instruction data. É aqui que os argumentos da instrução seriam
codificados, mas esta instrução initialize não requer nenhum argumento.
#[derive(Accounts)]pub struct Increment<'info> {#[account(mut,seeds = [COUNTER_SEED],bump)]pub counter: Account<'info, Counter>,pub authority: Signer<'info>,}
let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);
O contexto de conta Increment define as contas necessárias pela instrução
increment. O teste passa essas mesmas contas para o helper gerado
my_program::accounts::Increment:
counteré passado comocounter.authorityé passado comoauthority: payer.pubkey().
my_program::instruction::Increment {}.data()
cria o instruction data. É aqui que os argumentos da instrução seriam
codificados, mas esta instrução increment não requer nenhum argumento.
use {anchor_lang::{prelude::Pubkey,solana_program::{instruction::Instruction, system_program},AccountDeserialize, InstructionData, ToAccountMetas,},litesvm::LiteSVM,solana_keypair::Keypair,solana_message::{Message, VersionedMessage},solana_signer::Signer,solana_transaction::versioned::VersionedTransaction,};#[test]fn test_initialize() {let program_id = my_program::id();let payer = Keypair::new();let counter = Pubkey::find_program_address(&[my_program::constants::COUNTER_SEED],&program_id,).0;let mut svm = LiteSVM::new();let bytes = include_bytes!(concat!(env!("CARGO_TARGET_TMPDIR"),"/../deploy/my_program.so"));svm.add_program(program_id, bytes).unwrap();svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Initialize {}.data(),my_program::accounts::Initialize {payer: payer.pubkey(),counter,system_program: system_program::ID,}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 0);assert_eq!(counter_state.authority, payer.pubkey());let instruction = Instruction::new_with_bytes(program_id,&my_program::instruction::Increment {}.data(),my_program::accounts::Increment {counter,authority: payer.pubkey(),}.to_account_metas(None),);let blockhash = svm.latest_blockhash();let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();let res = svm.send_transaction(tx);assert!(res.is_ok());let counter_account = svm.get_account(&counter).unwrap();let mut data: &[u8] = &counter_account.data;let counter_state = my_program::state::Counter::try_deserialize(&mut data).unwrap();assert_eq!(counter_state.count, 1);assert_eq!(counter_state.authority, payer.pubkey());}
Configuração do projeto
Os arquivos raiz do projeto informam ao Anchor e ao Cargo como compilar, testar e implantar o programa. Para uma referência completa, consulte a documentação do Anchor para configuração do Anchor.toml e o Anchor CLI.
skip_local_validator = true[toolchain][features]resolution = trueskip-lint = false[programs.localnet]my_program = "82sFkffP9wxwpyfZyeaKHH2chQoJPUGsJZSPi9mrUuXd"[provider]cluster = "localnet"wallet = "~/.config/solana/id.json"[scripts]test = "cargo test"[hooks]
Is this page helpful?