Uma invocação entre programas (CPI) é a primitiva de composição do Solana: durante sua instrução, seu programa chama outro programa com uma lista de contas, dados de instrução e sementes opcionais de signatário de PDA. Anchor 0.32.1 envolve o caminho bruto invoke / invoke_signed em CpiContext, helpers gerados e contas de programa tipadas para que a composição seja legível sem ocultar as regras de tempo de execução.
Esta página é o mapa da seção. Use-a para posicionar CpiContext, CPIs assinadas por PDA, helpers do SPL Token, módulos de programa personalizados, contas restantes e verificações de privilégio em um contínuo antes de seguir as páginas de receita focadas.
As CPIs do Anchor ainda obedecem às mesmas regras do SVM que o código nativo. O chamado deve ser uma conta de programa executável. Cada conta que o chamado precisa deve aparecer na instrução do chamador (ou ser aninhada corretamente através de outras CPIs). Privilégios de signatário vêm de assinaturas de transação ou de invoke_signed com sementes que provam que seu programa possui um PDA. Contas mutáveis devem ser marcadas como graváveis na transação externa quando o chamado as gravar.
O que o Anchor adiciona é o empacotamento. CpiContext::new contém a conta AccountInfo do programa e uma struct de contas. CpiContext::new_with_signer (ou .with_signer) anexa slices de sementes para que o runtime trate o PDA como um signatário para essa instrução interna. anchor-spl constrói layouts padrão de Token / Token-2022. declare_program! transforma um IDL externo em um módulo cpi com structs de conta tipadas. .with_remaining_accounts anexa contas dinâmicas para roteadores, oráculos e fluxos multi-hop.
A superfície de risco é concentrada. Um ID de programa incorreto é código arbitrário em seu contexto de privilégio. Sementes de PDA incorretas falham na CPI ou autorizam a conta errada. Contas restantes não validadas são ataques de substituição. Assinar como um PDA de tesouraria em um programa não confiável é um roubo de cofre. Trate cada CPI como um limite de privilégio: fixe destinos, valide identidades e mapeie erros intencionalmente.
Uma CPI é execução aninhada síncrona dentro de uma instrução de transação. Seu manipulador é executado, então o runtime entra no chamado, então o controle retorna para que você possa continuar ou falhar. Não é uma segunda instrução de transação de nível superior. A atomicidade ainda se aplica: se a instrução externa falhar após uma CPI interna bem-sucedida, toda a instrução aborta e o estado é revertido para esse caminho de falha.
Clientes frequentemente compõem múltiplas instruções de nível superior. Isso não é CPI. CPI é necessária quando a lógica on-chain deve decidir a chamada, impor verificações intermediárias ou assinar como um PDA que o usuário não pode assinar.
O código nativo constrói uma Instruction { program_id, accounts, data } e chama invoke ou invoke_signed. O Anchor gera essas peças a partir de:
Peça
Representação Anchor
Programa de destino
Primeiro argumento para CpiContext::new / conta Program<T>
Metadados de conta
Campos de struct convertidos via ToAccountInfos / ToAccountMetas
Dados da instrução
Argumentos de função em token_interface::transfer_checked, cpi::foo gerado, etc.
Signatários de PDA
signer_seeds no contexto
Você ainda paga unidades de computação para o programa interno e ainda deve liberar empréstimos conflitantes antes da CPI em contas que você também muta localmente.
Manipulador de instrução externa (seu programa) | | 1. restrições já foram executadas nas contas do Context | 2. verificações opcionais de remaining_accounts | 3. construir CpiContext (programa + contas) | 4. optional with_signer / new_with_signer | 5. optional with_remaining_accounts v Runtime: invoke / invoke_signed | v process_instruction do Chamado | | Ok -> continuar manipulador externo | Err -> externo vê ProgramError (mapear ou propagar) v Ok(()) externo confirma instrução | qualquer Err aborta instrução
Valide antes de CPI sensível a privilégios sempre que possível. Um chamado não pode reentrar em seu programa - o runtime rejeita qualquer invocação de um programa já na pilha de instruções com ReentrancyNotAllowed - mas ele pode mutar contas que você já desserializou. Prefira o estilo verificações-efeitos-interações por esse motivo e reload() qualquer conta que você ler novamente após a CPI.
CPIs não assinadas (ou assinadas pelo usuário) usam CpiContext::new(program, accounts):
use anchor_lang::system_program::{self, Transfer};system_program::transfer( CpiContext::new( ctx.accounts.system_program.to_account_info(), Transfer { from: ctx.accounts.payer.to_account_info(), to: ctx.accounts.recipient.to_account_info(), }, ), lamports,)?;
O primeiro argumento é a conta de programa executável, não uma pubkey aleatória. Prefira Program<'info, System> (ou Token, ou tipos de programa gerados) para que o Anchor rejeite IDs de programa incorretos no momento da restrição. Veja Fundamentos de CPI no Anchor.
Quando um PDA é a autoridade (cofre, autoridade de mint, escrow), passe sementes que o runtime pode hashear de volta para esse endereço sob seu ID de programa:
use anchor_spl::token_interface::{self, TransferChecked};let bump = ctx.bumps.vault;// `key()` retorna uma Pubkey por valor - vincule-a a um `let` primeiro, ou o// temporário é descartado enquanto `seeds` ainda o empresta (E0716).let user_key = ctx.accounts.user.key();let seeds: &[&[u8]] = &[b"vault", user_key.as_ref(), &[bump]];let signer: &[&[&[u8]]] = &[seeds];token_interface::transfer_checked( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), TransferChecked { from: ctx.accounts.vault_ata.to_account_info(), mint: ctx.accounts.mint.to_account_info(), to: ctx.accounts.user_ata.to_account_info(), authority: ctx.accounts.vault.to_account_info(), }, signer, ), amount, ctx.accounts.mint.decimals,)?;
Regras que importam em produção:
A ordem e os componentes das sementes devem corresponder à derivação do PDA e às restrições #[account(seeds = ...)].
Prefira ctx.bumps.* em vez de bumps codificados após a inicialização.
A conta PDA deve ser o campo de autoridade que o chamado espera.
Múltiplos PDAs precisam de múltiplos grupos de sementes na fatia do signatário.
Estilo equivalente: construir CpiContext::new(...) e depois .with_signer(signer). Escolha um estilo por base de código. Padrões completos: CPIs Assinadas.
anchor-spl 0.32.1 expõe helpers como transfer_checked, mint_to, burn e approve que empacotam os metadados de conta corretos e dados de instrução para o programa SPL Token. anchor_spl::token::transfer ainda existe, mas está depreciado - ele não carrega o mint ou os decimais, então prefira transfer_checked em código novo.
use anchor_spl::token_interface::{self, TransferChecked};token_interface::transfer_checked( CpiContext::new( ctx.accounts.token_program.to_account_info(), TransferChecked { from: ctx.accounts.from_ata.to_account_info(), mint: ctx.accounts.mint.to_account_info(), to: ctx.accounts.to_ata.to_account_info(), authority: ctx.accounts.authority.to_account_info(), }, ), amount, ctx.accounts.mint.decimals,)?;
Checklist operacional:
Marque as contas de token de origem e destino como mut.
A autoridade deve ser um signatário ou um PDA com with_signer.
Passe o mesmo mint que ambos os ATAs referenciam e obtenha decimals dessa conta mint.
Para Token-2022 e suporte duplo, use anchor_spl::token_interface em toda parte - Interface<'info, TokenInterface> para o programa e InterfaceAccount<'info, Mint> / InterfaceAccount<'info, TokenAccount> para o estado. Nunca misture anchor_spl::token::Mint com InterfaceAccount.
Para outros programas Anchor (ou compatíveis com IDL), declare_program!(name) do Anchor 0.32.1 lê o JSON do IDL em tempo de compilação e gera funções name::cpi::... e structs name::cpi::accounts::....
Fixe o IDL na versão implantada do programa. IDLs desatualizados compilam limpo e falham (ou pior, decodificam incorretamente) em tempo de execução. Use o tipo gerado Program<'info, marketplace::program::Marketplace> (nomes variam por IDL) para que o ID do programa não possa ser substituído. Chamados não-Anchor ainda podem precisar de discriminadores manuais e invoke / invoke_signed. Veja CPI para Programas Personalizados.
Alguns chamados esperam uma cauda variável: rotas de hop, conjuntos de oráculos, taxas opcionais. O Anchor expõe contas além da struct fixa #[derive(Accounts)] como ctx.remaining_accounts. Encaminhe-as com:
Falhas internas aparecem como ProgramError (ou códigos de erro Anchor) para o chamador. Mapeie falhas opacas para suas variantes #[error_code] quando a UX do produto precisar de códigos de cliente claros. A simulação mostra logs aninhados; não confie em msg! verboso em caminhos de produção.
Cada CPI consome CU para configuração e para o trabalho do chamado. CPIs aninhadas e longas caminhadas de contas restantes somam. Orce e meça instruções quentes sob ferramentas locais Agave 4.1.1 antes da mainnet.
Um chamado não pode fazer CPI de volta ao seu programa. O runtime Agave rejeita qualquer invocação de um programa já na pilha de instruções com InstructionError::ReentrancyNotAllowed; apenas a auto-recursão direta é a exceção. Projetar em torno de um callback para si mesmo produz uma falha de transação difícil, não um padrão inteligente.
O que você ainda precisa projetar é a divergência de estado: um chamado pode mutar contas que você já desserializou, então suas cópias na memória ficam obsoletas no momento em que a CPI retorna. Complete transições de estado críticas antes de fazer CPI para fora (verificações-efeitos-interações), chame reload() em qualquer conta Anchor que você ler novamente depois, e lembre-se que instruções de nível superior separadas na mesma transação podem se intercalar contra suposições que seu manipulador fez. Isso é especialmente agudo para cofres, mints e contas de configuração compartilhadas.
Assinar como um PDA de alto valor é uma concessão deliberada de autoridade. Defina o escopo de quais instruções podem usar new_with_signer para sementes de tesouraria. Nunca anexe sementes de tesouraria a uma CPI cujo ID de programa ou seletor de instrução seja controlado pelo cliente. Prefira autoridades estreitas (um PDA de autoridade de mint, um PDA de cofre) em vez de um único PDA "deus" usado para todas as chamadas externas.
Testes devem cobrir mais do que caminhos felizes: ID de programa incorreto, mint incorreto, bump incorreto, mut ausente, trocas de ordem de contas restantes e erros personalizados do chamado. Validadores locais sob Solana CLI 3.0.10 exercitam SBF completo e logs. Mantenha fixtures IDL no repositório para declare_program! para que o CI não se desvie dos layouts implantados sem um bump deliberado.
Transações externas construídas com @solana/kit 7.0.0 (ou Anchor TS) devem listar todas as contas que a árvore completa de CPI tocará, com flags corretas de signatário e gravável. Uma flag gravável ausente falha em tempo de execução, mesmo que os tipos Rust pareçam bons. Quando contas restantes importam, publique o contrato de ordenação ao lado do IDL.
Conceito Errado: Restrições do Anchor validam totalmente o destino da CPI.
Restrições validam contas que você declarou. Elas não validam automaticamente todas as contas restantes ou todas as regras de negócios dentro de um programa de terceiros.
Conceito Errado: Qualquer PDA pode assinar se eu passar as sementes.
Apenas PDAs derivados sob seu ID de programa podem ser assinados pelo seu programa. O PDA de outro programa requer uma CPI para esse proprietário.
Conceito Errado: Program<'info, Token> é opcional se eu codificar a chave do programa Token nos dados.
Chaves codificadas em dados de instrução não são o mesmo que fixar a conta executável. Sempre passe e verifique o tipo da conta do programa.
Conceito Errado: CPI assinada é apenas para transferências de SOL.
Qualquer instrução que precise de uma autoridade PDA (transferência de token, mint, burn, approve, lista de marketplace personalizada) usa o mesmo mecanismo de sementes.
Conceito Errado: Contas restantes são "não verificadas, então são gratuitas".
Elas não são verificadas pelas macros de conta do Anchor até que você as verifique. Caudas não validadas são uma classe de exploit de alto nível.
Conceito Errado: Composição multi-instrução do cliente substitui CPI para custódia de PDA.
Se apenas o programa puder assinar como o cofre, o cliente não poderá enviar uma transferência de token independente como esse cofre. CPI com sementes é necessária.
Conceito Errado: Mapear todos os erros de CPI para um único SomethingFailed está bom.
Protótipos podem. Clientes de produção precisam de códigos estáveis e específicos para retentativas e resposta a incidentes.
Conceito Errado: declare_program! congela a segurança para sempre.
Atualizações do chamado mudam o comportamento sob o mesmo ID de programa. Reaudite após atualizações, mesmo quando o IDL ainda compila.
Uma chamada tipada para outro programa construída com CpiContext (e geralmente um módulo helper) que compila para invoke ou invoke_signed em tempo de execução.
Quando devo usar CpiContext::new versus new_with_signer?
Use new quando todos os signatários necessários já assinaram a transação externa. Use new_with_signer (ou .with_signer) quando um PDA do seu programa deve autorizar a instrução interna.
De onde vêm as sementes de signatário?
Os mesmos componentes de semente usados para derivar o PDA, mais o bump (frequentemente de ctx.bumps). Eles devem corresponder exatamente à derivação on-chain.
Como funcionam as transferências do SPL Token de um programa?
Inclua as contas de token, o mint e Interface<TokenInterface> (ou Program<Token> para clássico apenas), construa uma struct de conta TransferChecked e chame token_interface::transfer_checked com um CpiContext, passando amount e os decimals do mint. Autoridades PDA precisam de sementes de signatário.
Para que serve declare_program!?
Ele gera clientes de CPI em tempo de compilação a partir do IDL de um programa externo para que você obtenha structs de conta tipadas e helpers de instrução em vez de discriminadores criados manualmente.
Como as contas restantes se relacionam com o IDL?
Contas fixas vivem na struct de contas e no IDL. Contas extras que os clientes anexam após essa lista se tornam remaining_accounts e devem ser validadas e ordenadas de acordo com as regras do chamado.
Uma CPI pode falhar enquanto as minhas escritas de estado anteriores permanecem?
Se a instrução externa retornar Err após uma CPI, a instrução falha como uma unidade. Estruture o código para que você não dependa de sucesso parcial quando o caminho externo ainda puder falhar.
Por que fixar Program<T> em vez de UncheckedAccount para programas?
Program<T> verifica se a conta é o ID executável esperado (e executável). Contas de programa não verificadas convidam ataques de substituição.
Quão profundas podem ser as aninhadas as CPIs?
MAX_INSTRUCTION_STACK_DEPTH é 5. A instrução de nível superior ocupa a altura 1, então você obtém 4 níveis de CPI aninhada abaixo dela. Apenas o aninhamento consome profundidade: um manipulador que chama cinco programas diferentes um após o outro são cinco CPIs todas na altura 2 e nunca se aproximam do limite. Projete para árvores rasas de qualquer maneira - gráficos dinâmicos profundos são difíceis de raciocinar sobre CU e sobre a frescura do estado pós-CPI.
Preciso de CPI para ler a conta de outro programa?
Não. Ler dados de conta que lhe foram passados não requer CPI. CPI é para executar a lógica de instrução de outro programa.
Como os clientes devem preparar contas para instruções ricas em CPI?
Liste todas as contas que o programa externo e todos os chamados aninhados precisarão, com flags corretas de signatário/gravável, correspondendo ao IDL do programa e à especificação de contas restantes, usando @solana/kit 7.0.0 ou ferramentas de cliente Anchor.