Anchor CPIs In Detail
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.
Resumo
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.
Fundamentos
O que é uma CPI (e o que não é)
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.
Por que o Anchor envolve invoke
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::transfer, 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.
Modelo de privilégio em uma tabela
| Fonte de privilégio | Como aparece na CPI |
|---|---|
| Assinatura do usuário / keypair | Conta is_signer true da transação externa |
| PDA do seu programa | invoke_signed / CpiContext::new_with_signer com sementes correspondentes |
| PDA de outro programa | Você não pode assiná-lo; faça CPI para o programa proprietário em vez disso |
| Gravável | AccountMeta externa deve permitir gravações se o chamado gravar |
O runtime não inventa autoridade. Ele apenas verifica assinaturas já presentes e provas de sementes que você fornece para o seu ID de programa.
Onde cada página de receita se encaixa
| Tópico | Papel |
|---|---|
| Fundamentos de CPI no Anchor | Padrões CpiContext e primeiros exemplos |
| CPIs do SPL Token | anchor-spl transfer, mint, burn, interface Token-2022 |
| CPIs Assinadas | Sementes de PDA, bumps, grupos multi-signatários |
| CPI para Programas Personalizados | declare_program! e módulos com IDL fixado |
| Passando Contas Restantes | Caudas de contas dinâmicas |
| Segurança de CPI | Fixação de programa, reentrada, limites de privilégio |
Mecânicas
Uma CPI é um pipeline fixo: validar contas externas, construir contexto, sementes e contas restantes opcionais, invocar, lidar com o resultado.
Pipeline de CPI de ponta a ponta
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. Prefira o estilo verificações-efeitos-interações quando um chamado puder reentrar em seu programa.
1. CpiContext::new
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.
2. CPIs Assinadas com Sementes de PDA
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:
let bump = ctx.bumps.vault;
let seeds: &[&[u8]] = &[b"vault", user.key().as_ref(), &[bump]];
let signer = &[seeds];
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.vault_ata.to_account_info(),
to: ctx.accounts.user_ata.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
},
signer,
),
amount,
)?;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.
3. CPIs do SPL Token via anchor-spl
anchor-spl 0.32.1 expõe helpers como transfer, mint_to, burn e approve que empacotam os metadados de conta corretos e dados de instrução para o programa SPL Token.
use anchor_spl::token::{self, Transfer};
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.from_ata.to_account_info(),
to: ctx.accounts.to_ata.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
amount,
)?;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. - Valide o alinhamento do mint entre ATAs quando os valores representam o mesmo ativo.
- Para Token-2022 e suporte duplo, use
anchor_spl::token_interfacecomInterface<'info, TokenInterface>em vez de codificar apenas o Token clássico.
Profundidade da receita: CPIs do SPL Token.
4. CPI para programas personalizados
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::....
declare_program!(marketplace);
marketplace::cpi::list_item(
CpiContext::new(
ctx.accounts.marketplace_program.to_account_info(),
marketplace::cpi::accounts::ListItem { /* espelhar contas do IDL */ },
),
price,
)?;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.
5. Contas restantes
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:
let mut cpi_ctx = CpiContext::new(
ctx.accounts.target_program.to_account_info(),
target::cpi::accounts::Handle { /* campos fixos */ },
);
cpi_ctx = cpi_ctx.with_remaining_accounts(ctx.remaining_accounts.to_vec());
target::cpi::handle(cpi_ctx)?;Antes de encaminhar:
- Limite o comprimento (
require!(len <= MAX)). - Verifique proprietário, listas de permissão de chaves, discriminadores e frescor conforme exigido pelo modelo de ameaça.
- Preserve a ordem documentada pelo chamado e pelos construtores do seu cliente (
@solana/kit7.0.0 ou clientes Anchor).
Detalhe: Passando Contas Restantes.
6. Erros e computação
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.
Avançado
Reentrada e estado compartilhado
Chamados podem fazer CPI de volta ao seu programa se clientes (ou programas intermediários) incluírem seu ID de programa e contas. Projete para reentrada: defina flags em andamento antes da CPI, complete transições de estado críticas antes de ceder e nunca assuma invariantes que uma entrada recursiva possa quebrar em andamento. Isso é especialmente agudo para cofres, mints e contas de configuração compartilhadas.
Minimização de privilégios
Assinar como um PDA de alto valor é uma concessão deliberada de autoridade. Escopo 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.
Composição híbrida
Forma comum de produção:
- Depósito de token assinado pelo usuário em um ATA de cofre (sem assinatura de PDA).
- Lógica do programa atualiza o estado interno.
- CPI assinada por PDA para um módulo IDL de DEX, mercado de empréstimos ou marketplace.
- Contas restantes opcionais para hops de rota validadas contra uma lista de permissão.
Documente para auditores quais IDs de programa são alcançáveis, quais sementes assinam e quais formas de contas restantes os clientes podem anexar.
Testando CPIs
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.
Clientes e metadados de conta
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.
Conceitos Errôneos Comuns
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.
FAQs
O que é uma CPI em termos de Anchor?
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 contas de token e Program<Token> (ou interface Token), construa uma struct de conta Transfer e chame token::transfer com um CpiContext. 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?
O runtime impõe uma profundidade máxima de chamada de CPI. Projete para árvores rasas; gráficos dinâmicos profundos são difíceis de raciocinar sobre CU e reentrada.
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.
Para onde devo ir a seguir nesta seção?
Comece com Fundamentos de CPI no Anchor, depois CPIs do SPL Token e CPIs Assinadas. Adicione CPI para Programas Personalizados e Passando Contas Restantes ao compor externamente. Feche o ciclo com Segurança de CPI.
Relacionado
- Fundamentos de CPI no Anchor - Construtores
CpiContexte primeiros padrões de trabalho - CPIs do SPL Token -
anchor-spltransfer, mint, burn e interface Token-2022 - CPIs Assinadas - Sementes de PDA, bumps e disciplina
with_signer - CPI para Programas Personalizados - Módulos IDL
declare_program!e higiene de atualização - Passando Contas Restantes - Caudas dinâmicas, ordem e limites
- Segurança de CPI - Fixação de programa, reentrada e limites de privilégio
Versões da Stack: Esta página foi escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1 e @solana/kit 7.0.0.