CPIs Explicadas
Uma invocação entre programas (CPI) é como um programa on-chain pede a outro programa para executar uma instrução dentro da mesma transação. Seu manipulador constrói uma Instruction (ID do programa, metadados de contas, dados) e passa uma fatia correspondente de valores AccountInfo, e o runtime carrega o chamado e o executa antes que o controle retorne para você.
As CPIs são o mecanismo por trás da composabilidade do Solana: cofres que movem SOL, garantias que transferem tokens SPL, roteadores que chamam DEXes e qualquer fluxo onde as regras de propriedade o forçam a passar por outro programa em vez de escrever suas contas diretamente.
Esta página é o guarda-chuva para Invocações entre Programas. Páginas irmãs aprofundam nos conceitos básicos, passagem de contas, invoke vs invoke_signed, alvos do System e Token, limites de profundidade, reentrância e padrões. Aqui você obtém um modelo coeso para que essas páginas se encaixem como zooms, não como histórias separadas.
Resumo
- Uma CPI é uma chamada aninhada para outro programa: o chamador fornece dados de instrução e uma lista de contas com privilégios restritos; o chamado executa sob os mesmos bloqueios de transação, assinaturas e orçamento de computação.
- Por que Importa: Você não pode reescrever contas que pertencem a outro programa. Transferências, cunhagens, criação de contas e a maioria das integrações de protocolo exigem CPI para o programa proprietário. Contas erradas, caminho de assinatura errado ou ordenação insegura produzem transações falhas ou bugs de segurança.
- Conceitos Chave: Instruction, AccountMeta / AccountInfo, propagação de privilégio,
invoke,invoke_signed, sementes de PDA, profundidade de CPI, CPI do System Program, CPI do SPL Token, checks-effects-interactions (verificações-efeitos-interações), chamados em lista de permissões. - Quando Usar: Sempre que seu programa precisar criar contas, mover SOL ou tokens, cunhar/queimar sob uma autoridade de programa, ou compor com outro protocolo on-chain em uma única transação atômica.
- Limitações / Trade-offs: A profundidade aninhada é finita, a CU (Compute Units) é compartilhada, toda conta deve ser listada antecipadamente, e IDs de programas chamados arbitrários são uma superfície de confiança. O sucesso atômico de múltiplos programas é poderoso; a falha aborta toda a transação.
- Tópicos Relacionados: Noções Básicas de CPI, invoke vs invoke_signed, CPIs do System e Token, limites de profundidade, reentrância, padrões de composabilidade.
Fundamentos
Programas Solana são executáveis sem estado. Saldos duráveis e layouts vivem em contas pertencentes a programas específicos. A regra de "somente o proprietário escreve" é absoluta: somente o Token Program pode reescrever os dados de uma conta de token; somente o System Program executa o ciclo de vida usual de criação/atribuição/transferência para contas de propriedade do sistema; somente seu programa pode reescrever contas que ele possui.
Quando sua lógica precisa que o estado de outra pessoa seja alterado, você não mexe em bytes. Você faz CPI para o proprietário com a instrução que esse programa define.
No nível da API, uma CPI se parece com a construção da mesma forma que um cliente enviaria como uma instrução de nível superior:
Transação Externa
+-- Instrução: SeuPrograma
| contas: [usuario, cofre, token_origem, token_destino, programa_token, sistema, ...]
| dados: seu discriminador + argumentos
|
| (dentro de SeuPrograma)
| invoke / invoke_signed
| |
| v
+-- Instrução Aninhada: TokenProgram (ou System, DEX, ...)
contas: subconjunto da lista externa (ordem e flags corretas)
dados: transferir / cunhar / criar_conta / ...Três fatos decorrem dessa imagem:
- Mesma transação, mesma atomicidade. Se o chamado falhar, sua instrução falha e toda a transação é revertida.
- Sem descoberta ambiente. Toda conta que a chamada aninhada necessita deve já estar na lista de contas da transação externa (com privilégios de signatário/gravável utilizáveis). CPIs não buscam chaves não declaradas em tempo de execução.
- Privilégios não crescem magicamente. As capacidades graváveis e de signatário na CPI são limitadas pelo que a mensagem externa já autorizou, mais assinaturas de PDA que seu programa pode adicionar com
invoke_signed.
Clientes (incluindo aqueles construídos com @solana/kit 7.0.0) ainda montam a mensagem de nível superior: eles listam programas e contas, assinam com carteiras e definem o orçamento de computação. Seu programa então costura chamadas aninhadas a partir desse mundo fechado de contas.
Mecânicas e Interações
Construindo a instrução aninhada
Uma CPI usa solana_program::instruction::Instruction:
| Campo | Papel |
|---|---|
program_id | Chave pública do programa chamado |
accounts | Vec<AccountMeta>: chave pública, is_signer, is_writable |
data | Bytes opacos que o chamado desserializa (opcode + argumentos) |
Você passa essa instrução para invoke ou invoke_signed com uma fatia de clones de AccountInfo cujos ordem e chaves correspondem aos metadados. Prefira helpers oficiais (system_instruction::transfer, spl_token::instruction::transfer) para que o layout dos dados e a ordem dos metadados permaneçam corretos.
Passagem de contas e regras de privilégio
Listas de contas para CPIs são uma superfície de segurança, não apenas contabilidade.
- Ordem deve corresponder ao que o chamado espera (helpers codificam isso; programas customizados documentam).
- Gravável deve ser o mínimo privilégio: marque como gravável apenas contas que o chamado precisa mutar.
- Signatário em um metadado significa que a conta deve ser um signatário reconhecido para essa chamada aninhada: seja uma carteira que assinou a transação externa, ou um PDA que seu programa assina via sementes.
- Contas de programa (Token, System, ou um programa parceiro) são geralmente entradas somente leitura para que o runtime possa carregar o chamado.
Bugs de escalonamento de privilégio parecem marcar uma conta como gravável ou signatária na CPI quando a transação externa nunca pretendeu essa capacidade. Valide flags da AccountInfo externa antes de promovê-las para metadados aninhados. Veja Passando Contas para CPIs para a lista de verificação operacional.
invoke vs invoke_signed
invoke | invoke_signed | |
|---|---|---|
| Encaminha assinaturas da transação externa | Sim | Sim |
| Adiciona "assinaturas" de PDA a partir de sementes | Não | Sim |
| Uso típico | Autoridade assinada pelo usuário já presente | PDA de cofre, PDA de autoridade de cunhagem, garantia de propriedade do programa |
| Modo de falha se mal utilizado | MissingRequiredSignature quando o PDA deve autorizar | Sementes/bump errados → falha na verificação de assinatura |
// Carteira (ou outro signatário externo) já é is_signer na conta
invoke(&ix, &[from.clone(), to.clone(), authority.clone(), token_program.clone()])?;
// Autoridade PDA: programa prova sementes + bump sob seu ID de programa
invoke_signed(
&ix,
&[vault.clone(), to.clone(), system_program.clone()],
&[&[b"vault", user.key.as_ref(), &[bump]]],
)?;invoke_signed não inventa assinaturas de carteira. Ele apenas permite que o programa atual autorize PDAs derivados de seu ID de programa e das fatias de sementes fornecidas. O design das sementes e a disciplina de bump pertencem aos PDAs; a escolha da CPI é simplesmente "o chamado precisa de um PDA como signatário?" Comparação completa: invoke vs invoke_signed.
Limites de profundidade e computação
O runtime limita a profundidade das CPIs aninhadas. No Solana atual (incluindo Agave 4.1.1), a profundidade máxima de CPI é 4. Árvores profundas (roteador → DEX → token → hook de transferência → …) atingem o limite rapidamente.
As unidades de computação são por transação. Chamadas aninhadas não recebem uma nova carteira de CU; chamados pesados e logs multiplicam o custo sob o orçamento padrão de ~1.4M CU (ajustável via instruções de Compute Budget dentro dos limites do protocolo). Projete de forma plana quando puder: uma instrução bem formatada com algumas CPIs é melhor que uma cadeia de chamadas profunda. Detalhes: Profundidade e Limites de CPI.
Alvos do dia a dia: System e SPL Token
CPIs do System Program criam contas, alocam espaço, atribuem proprietários e transferem lamports. O fluxo típico para uma nova conta de propriedade do programa: mínimo isento de aluguel de Rent::get(), então create_account (ou padrões de alocação/atribuição) com o pagador como signatário. Movimentação de SOL financiada por PDA ou originada de PDA precisa de invoke_signed. Veja CPI para o System Program.
CPIs do SPL Token cunham, transferem, queimam, aprovam e operações relacionadas. Você nunca reescreve os bytes da conta de token diretamente; você constrói uma instrução com spl_token::instruction (ou equivalentes Token-2022) e invoke / invoke_signed com o ID correto do programa de token, cunhagem, autoridades e contas de token. Valide se a cunhagem corresponde, os decimais (valores brutos), o estado de congelamento e o ID do programa Token vs Token-2022. Autoridades de cunhagem ou congelamento de PDA usam invoke_signed. Veja CPI para SPL Token.
Reentrância e segurança
Ao contrário de ambientes com um mutex global padrão, Solana não oferece um bloqueio de reentrância gratuito. Outro programa para o qual você faz CPI pode fazer CPI de volta para você (hooks de transferência, callbacks, IDs de programa maliciosos fornecidos pelo usuário). Trate CPIs externas como efeitos colaterais não confiáveis.
Regras práticas:
- Verificações → efeitos → interações: valide, atualize e serialize seu estado, então faça a CPI externa.
- Flags idempotentes: uma vez que um saque ou liquidação é marcado como concluído, a reentrada não pode pagar duas vezes.
- Lista de permissões de chamados quando o ID do programa não é uma constante fixa (roteadores, plugins).
- Não assuma entrada única se hooks do Token-2022 ou programas parceiros puderem chamá-lo no meio do fluxo.
Os limites de profundidade limitam a pilha; eles não tornam segura a ordenação insegura. Veja Reentrância e Segurança.
Considerações Avançadas e Aplicações
Composabilidade é o produto de CPIs feitas cuidadosamente: uma transação pode validar um usuário, atualizar um ledger, trocar em um DEX e depositar em um cofre, tudo ou nada.
| Padrão | O que faz | Formato da CPI | Cuidado com |
|---|---|---|---|
| Escrow / cofre | Programa custodia ativos sob um PDA | invoke_signed para System ou Token | Sementes, bumps, campos de autoridade |
| Roteador / agregador | Uma instrução de entrada se ramifica para parceiros | Múltiplos invokes, IDs de programa em lista de permissões | Profundidade, CU, slippage, contas restantes |
| Adaptador | Normaliza um protocolo estrangeiro sob sua API | Wrapper de CPI fino + seu estado | Atualizações de parceiros e derivação do layout da conta |
| Multi-ix composta pelo cliente | Sem CPI aninhada; várias instruções de nível superior | Zero ou poucas CPIs em seu programa | Orquestração de programa único mais fraca; ainda atômica como uma transação |
Contas restantes permitem que clientes passem caudas de contas extensíveis (pools extras, oráculos, programas de memo) sem codificar em hard cada slot em sua struct Accounts. Documente o layout por versão e valide cada chave e proprietário antes da CPI.
Address Lookup Tables (ALTs) abordam o tamanho da transação, não a profundidade da CPI. Elas ajudam quando as listas de contas explodem; elas não aumentam o limite de profundidade 4.
Anchor 0.32.1 gera helpers de CPI tipados (CpiContext, wrappers invoke_signed, anchor_spl) para que metadados e discriminadores permaneçam alinhados com os IDLs. O modelo de runtime é o mesmo: mesmos privilégios, mesma profundidade, mesma necessidade de ordenação CEI. Rust nativo com solana_program é a mesma superfície de syscall com mais fiação manual.
Para revisões de arquitetura, pergunte: qual programa possui cada conta que tocamos; quais signatários (carteira ou PDA) autorizam cada chamada aninhada; se os IDs dos programas chamados são fixos ou em lista de permissões; se o estado é finalizado antes da CPI externa; se a árvore de chamadas se encaixa nos limites de profundidade e CU sob simulação realista nas ferramentas Agave 4.1.1 / CLI 3.0.10.
Equívocos Comuns
- "CPI é apenas uma chamada de API interna estilo HTTP." É uma instrução aninhada mediada pelo runtime sob os mesmos bloqueios, assinaturas e orçamento de CU, com regras rígidas de contas e profundidade.
- "Meu programa pode escrever saldos de token se eu passar a conta de token." Somente o Token Program possui esses bytes. Você deve fazer CPI com uma instrução de transferência/cunhagem/queima válida e autoridade.
- "
invoke_signedme permite forjar qualquer assinatura." Ele apenas autoriza PDAs para seu ID de programa com as sementes que você fornece. Ele não cria assinaturas de carteira. - "Se eu conhecer uma chave pública, a CPI pode carregá-la." Contas não declaradas não estão disponíveis. Clientes devem incluir todas as contas que o caminho de chamada completo necessita.
- "Programas aninhados recebem seus próprios orçamentos de computação." Todo o trabalho aninhado consome o pool de CU da transação.
- "Reentrância é um problema apenas do EVM." Hooks e callbacks a tornam real no Solana; ordene atualizações de estado antes de CPIs externas e coloque alvos não confiáveis em lista de permissões.
- "Composição mais profunda é sempre melhor." Profundidade 4 e tetos de CU penalizam árvores profundas; designs mais planos e transações multi-instrução do lado do cliente são frequentemente mais saudáveis.
- "Marcar tudo como gravável é mais seguro." Isso expande o raio de explosão se um chamado ou bug mutar mais do que o pretendido. Use o mínimo privilégio.
FAQs
O que é uma CPI em uma frase?
Uma invocação entre programas é seu programa executando a instrução de outro programa no meio do manipulador, dentro da mesma transação atômica, com uma lista de contas declarada e orçamento de computação compartilhado.
Por que não posso simplesmente editar os dados da conta de outro programa?
O runtime impõe a regra de "somente o proprietário escreve": somente o programa proprietário pode mutar os dados de uma conta. CPI é como você pede a esse proprietário para aplicar uma instrução suportada.
O que deve ser verdade sobre as contas que passo para uma CPI?
Elas devem já aparecer na transação externa, corresponder à ordem e papéis esperados pelo chamado, e carregar privilégios de signatário/gravável que a mensagem externa (mais quaisquer sinais de PDA) legitimamente permite.
Quando uso invoke vs invoke_signed?
Use invoke quando todos os signatários necessários já assinaram a transação. Use invoke_signed quando um PDA derivado do seu programa deve atuar como signatário (cofres, autoridades de cunhagem, garantias de propriedade do programa).
Qual é o limite de profundidade da CPI?
A profundidade máxima aninhada de CPI é 4 no Solana atual (Agave 4.1.1). Planeje os grafos de chamadas para que os programas de Token, hooks e parceiros não esgotem a pilha.
As CPIs recebem orçamentos de unidades de computação separados?
Não. A execução aninhada consome o mesmo pool de CU da transação. Simule caminhos com múltiplas CPIs e defina instruções de Compute Budget quando necessário.
Como as CPIs do System Program se encaixam no trabalho diário do programa?
Elas criam e financiam contas e movem SOL. create_account e transfer isentos de aluguel são os blocos de construção comuns; fontes de PDA precisam de invoke_signed.
Como as CPIs do SPL Token se encaixam no trabalho diário do programa?
Operações de cunhagem, transferência, queima e autoridade passam pelo Token ou Token-2022 via construtores de instrução e CPI. Valide o ID do programa, cunhagem, valores e autoridades antes de chamar.
Qual é o erro de segurança de CPI mais comum?
Atualizar saldos ou flags após uma CPI externa (ou chamar IDs de programa não listados em lista de permissões), o que permite que a reentrância ou chamados maliciosos observem estado inconsistente.
Um chamado pode chamar de volta para meu programa?
Sim, dentro dos limites de profundidade. Projete manipuladores para serem seguros se reentrados: finalize o estado primeiro, use flags de liquidação e trate hooks como caminhos de primeira classe.
O Anchor remove a necessidade de entender CPIs?
Não. Anchor 0.32.1 melhora a tipagem e a ergonomia; você ainda escolhe sementes invoke vs signed, passa contas corretamente, respeita profundidade/CU e ordena atualizações de estado de forma segura.
Como os clientes devem se preparar para uma instrução rica em CPI?
Inclua todas as contas que o caminho aninhado completo necessita (incluindo programas e sysvars), defina um orçamento de computação adequado e prefira ALTs quando a contagem de contas inflar o tamanho da mensagem.
A composição multi-instrução sem CPI é uma alternativa válida?
Sim. Várias instruções de nível superior em uma transação permanecem atômicas sem profundidade aninhada. Use isso quando você não precisar de lógica intermediária imposta pelo programa entre as etapas.
Para onde devo ir depois desta página?
Comece com Noções Básicas de CPI e invoke vs invoke_signed, depois alvos System/Token, profundidade, reentrância e Padrões de Composabilidade à medida que você projeta fluxos reais.
Relacionados
- Noções Básicas de CPI - primeiros padrões
invokee construção de Instruction - invoke vs invoke_signed - assinaturas de carteira vs assinatura de sementes de PDA
- CPI para o System Program - crie contas e transfira SOL
- CPI para SPL Token - cunhe, transfira e queime via programa Token
- Profundidade e Limites de CPI - limite de profundidade, pressão de CU, designs planos
- Padrões de Composabilidade - roteadores, adaptadores e fluxos de múltiplos programas
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.