Una invocación entre programas (CPI) es el primitivo de composición de Solana: durante tu instrucción, tu programa llama a otro programa con una lista de cuentas, datos de instrucción y semillas de firmante PDA opcionales. Anchor 0.32.1 envuelve la ruta invoke / invoke_signed nativa en CpiContext, ayudantes generados y cuentas de programa tipadas para que la composición sea legible sin ocultar las reglas de tiempo de ejecución.
Esta página es el mapa de la sección. Úsala para colocar CpiContext, CPIs firmadas por PDA, ayudantes de Token SPL, módulos de programa personalizados, cuentas restantes y verificaciones de privilegios en un continuo antes de seguir las páginas de recetas enfocadas.
Las CPIs de Anchor todavía obedecen las mismas reglas de SVM que el código nativo. El llamado debe ser una cuenta de programa ejecutable. Cada cuenta que el llamado necesita debe aparecer en la instrucción del llamador (o estar anidada correctamente a través de CPIs adicionales). Los privilegios de firmante provienen de las firmas de la transacción o de invoke_signed con semillas que prueban que tu programa posee una PDA. Las cuentas mutables deben marcarse como escribibles en la transacción externa cuando el llamado las escriba.
Lo que Anchor añade es el empaquetado. CpiContext::new contiene la cuenta AccountInfo del programa y una estructura de cuentas. CpiContext::new_with_signer (o .with_signer) adjunta porciones de semillas para que el tiempo de ejecución trate a la PDA como firmante para esa instrucción interna. anchor-spl construye diseños estándar de Token / Token-2022. declare_program! convierte una IDL externa en un módulo cpi con estructuras de cuentas tipadas. .with_remaining_accounts anexa cuentas dinámicas para enrutadores, oráculos y flujos de varios saltos.
La superficie de riesgo está concentrada. Un ID de programa incorrecto es código arbitrario en tu contexto de privilegios. Semillas de PDA incorrectas fallan la CPI o autorizan la cuenta incorrecta. Las cuentas restantes no validadas son ataques de sustitución. Firmar como una PDA de tesorería en un programa no confiable es un vaciado de bóveda. Trata cada CPI como un límite de privilegios: ancla los objetivos, valida las identidades y mapea los errores intencionalmente.
Una CPI es ejecución anidada síncrona dentro de una instrucción de transacción. Tu manejador se ejecuta, luego el tiempo de ejecución entra en el llamado, luego el control regresa para que puedas continuar o fallar. No es una segunda instrucción de transacción de nivel superior. La atomicidad todavía se aplica: si la instrucción externa falla después de una CPI interna exitosa, toda la instrucción se aborta y el estado se revierte para esa ruta de fallo.
Los clientes a menudo componen múltiples instrucciones de nivel superior en su lugar. Eso no es CPI. La CPI es necesaria cuando la lógica en cadena debe decidir la llamada, aplicar verificaciones intermedias o firmar como una PDA que el usuario no puede firmar.
El código nativo construye una Instruction { program_id, accounts, data } y llama a invoke o invoke_signed. Anchor genera estas piezas a partir de:
Pieza
Representación de Anchor
Programa de destino
Primer argumento de CpiContext::new / cuenta Program<T>
Metas de cuenta
Campos de estructura convertidos a través de ToAccountInfos / ToAccountMetas
Datos de instrucción
Argumentos de función en token_interface::transfer_checked, cpi::foo generado, etc.
Firmantes PDA
signer_seeds en el contexto
Todavía pagas unidades de cómputo por el programa interno y aún debes liberar préstamos conflictivos antes de la CPI en cuentas que también mutas localmente.
Manejador de instrucción externa (tu programa) | | 1. las restricciones ya se ejecutaron en las cuentas del Contexto | 2. verificaciones opcionales de remaining_accounts | 3. construir CpiContext (programa + cuentas) | 4. opcional with_signer / new_with_signer | 5. opcional with_remaining_accounts v Tiempo de ejecución: invoke / invoke_signed | v Callee process_instruction | | Ok -> continuar manejador externo | Err -> el externo ve ProgramError (mapear o propagar) v Outer Ok(()) confirma la instrucción | cualquier Err aborta la instrucción
Valida antes de la CPI sensible a privilegios siempre que sea posible. Un llamado no puede volver a entrar en tu programa; el tiempo de ejecución rechaza cualquier invocación de un programa que ya esté en la pila de instrucciones con ReentrancyNotAllowed, pero puede mutar cuentas que ya deserializaste. Prefiere el estilo de verificaciones-efectos-interacciones por esa razón, y reload() cualquier cuenta que vuelvas a leer después de la CPI.
Las CPIs sin firmar (o firmadas por el usuario) usan 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,)?;
El primer argumento es la cuenta de programa ejecutable, no una clave pública aleatoria. Prefiere Program<'info, System> (o Token, o tipos de programa generados) para que Anchor rechace IDs de programa incorrectos en el momento de la restricción. Ver Fundamentos de CPI en Anchor.
Cuando una PDA es la autoridad (bóveda, autoridad de emisión, escrow), pasa semillas que el tiempo de ejecución puede hashear de vuelta a esa dirección bajo tu ID de programa:
use anchor_spl::token_interface::{self, TransferChecked};let bump = ctx.bumps.vault;// `key()` devuelve una Pubkey por valor; asígnala a una `let` primero, o// la temporal se descarta mientras `seeds` todavía la toma prestada (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,)?;
Reglas que importan en producción:
El orden y los componentes de las semillas deben coincidir con la derivación de la PDA y las restricciones #[account(seeds = ...)].
Prefiere ctx.bumps.* sobre los bumps codificados en duro después de la inicialización.
La cuenta PDA debe ser el campo de autoridad que el llamado espera.
Múltiples PDAs necesitan múltiples grupos de semillas en el slice de firmante.
Estilo equivalente: construir CpiContext::new(...) y luego .with_signer(signer). Elige un estilo por base de código. Patrones completos: CPIs Firmadas.
anchor-spl 0.32.1 expone ayudantes como transfer_checked, mint_to, burn y approve que empaquetan las metas de cuenta y los datos de instrucción correctos para el programa SPL Token. anchor_spl::token::{transfer, Transfer} todavía existe pero está obsoleto; no transporta la mint ni los decimales, así que prefiere transfer_checked en código nuevo.
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,)?;
Lista de verificación operativa:
Marca las cuentas de token de origen y destino como mut.
La autoridad debe ser un firmante o una PDA con with_signer.
Pasa la misma mint que ambas ATAs referencian, y toma los decimals de esa cuenta de mint.
Para Token-2022 y soporte dual, usa anchor_spl::token_interface en todo momento; Interface<'info, TokenInterface> para el programa y InterfaceAccount<'info, Mint> / InterfaceAccount<'info, TokenAccount> para el estado. Nunca mezcles anchor_spl::token::Mint con InterfaceAccount.
Para otros programas de Anchor (o compatibles con IDL), declare_program!(name) de Anchor 0.32.1 lee la IDL JSON en tiempo de compilación y genera funciones name::cpi::... y estructuras name::cpi::accounts::....
Ancla la IDL a la versión desplegada del programa. Las IDLs obsoletas compilan limpiamente y fallan (o peor, decodifican incorrectamente) en tiempo de ejecución. Usa el tipo generado Program<'info, marketplace::program::Marketplace> (los nombres varían según la IDL) para que el ID del programa no pueda ser sustituido. Los llamados que no son de Anchor aún pueden necesitar discriminadores manuales e invoke / invoke_signed. Ver CPI a Programas Personalizados.
Algunos llamados esperan una cola variable: rutas de salto, conjuntos de oráculos, tarifas opcionales. Anchor expone las cuentas más allá de la estructura fija #[derive(Accounts)] como ctx.remaining_accounts. Reenvíalas con:
Los fallos internos aparecen como ProgramError (o códigos de error de Anchor) para el llamador. Mapea fallos opacos a tus variantes #[error_code] cuando la experiencia de usuario del producto necesite códigos de cliente claros. La simulación muestra registros anidados; no confíes en msg! verbosos en rutas de producción.
Cada CPI consume CU para la configuración y para el trabajo del llamado. Las CPIs anidadas y los recorridos extensos de cuentas restantes suman. Presupuesta y mide las instrucciones "calientes" bajo las herramientas locales de Agave 4.1.1 antes de la red principal.
Un llamado no puede hacer CPI de vuelta a tu programa. El tiempo de ejecución de Agave rechaza cualquier invocación de un programa que ya esté en la pila de instrucciones con InstructionError::ReentrancyNotAllowed; la autorrecursión directa es la única excepción. Diseñar alrededor de una devolución de llamada a ti mismo produce un fallo de transacción duro, no un patrón inteligente.
Lo que aún debes diseñar es la divergencia de estado: un llamado puede mutar cuentas que ya deserializaste, por lo que tus copias en memoria se vuelven obsoletas en el momento en que la CPI regresa. Completa las transiciones de estado críticas antes de salir con CPI (verificaciones-efectos-interacciones), llama a reload() en cualquier cuenta de Anchor que vuelvas a leer después, y recuerda que las instrucciones de nivel superior separadas en la misma transacción pueden intercalarse contra las suposiciones que hizo tu manejador. Esto es especialmente agudo para bóvedas, emisiones y cuentas de configuración compartidas.
Firmar como una PDA de alto valor es una concesión deliberada de autoridad. Define el alcance de qué instrucciones pueden usar new_with_signer para semillas de tesorería. Nunca adjuntes semillas de tesorería a una CPI cuyo ID de programa o selector de instrucción sea controlado por el cliente. Prefiere autoridades estrechas (una PDA de autoridad de emisión, una PDA de bóveda) sobre una única PDA "dios" utilizada para cada llamada externa.
Las pruebas deben cubrir más que los caminos felices: ID de programa incorrecto, mint incorrecto, bump incorrecto, falta de mut, intercambios de orden de cuentas restantes y errores personalizados del llamado. Los validadores locales bajo Solana CLI 3.0.10 ejercitan SBF completo y registros. Mantén las "fixtures" de IDL en el repositorio para declare_program! para que la CI no se desvíe de los diseños desplegados sin un bump deliberado.
Las transacciones externas construidas con @solana/kit 7.0.0 (o Anchor TS) deben listar cada cuenta que el árbol completo de CPI tocará, con las banderas correctas de firmante y escribible. Una bandera escribible faltante falla en tiempo de ejecución incluso si los tipos de Rust parecen correctos. Cuando las cuentas restantes importan, publica el contrato de ordenación junto a la IDL.
Concepto erróneo: Las restricciones de Anchor validan completamente el objetivo de la CPI.
Las restricciones validan las cuentas que declaraste. No validan automáticamente todas las cuentas restantes ni todas las reglas de negocio dentro de un programa de terceros.
Concepto erróneo: Cualquier PDA puede firmar si paso semillas.
Solo las PDAs derivadas bajo tu ID de programa pueden ser firmadas por tu programa. La PDA de otro programa requiere una CPI a ese propietario.
Concepto erróneo: Program<'info, Token> es opcional si codifico en duro la clave del programa Token en los datos.
Las claves codificadas en duro en los datos de instrucción no son lo mismo que anclar la cuenta ejecutable. Siempre pasa y verifica el tipo de la cuenta del programa.
Concepto erróneo: La CPI firmada es solo para transferencias de SOL.
Cualquier instrucción que necesite una autoridad PDA (transferencia de token, emisión, quema, aprobación, lista de mercado personalizada) utiliza el mismo mecanismo de semillas.
Concepto erróneo: Las cuentas restantes son "no verificadas, por lo que son gratuitas".
No son verificadas por las macros de cuenta de Anchor hasta que tú las verificas. Las colas no validadas son una clase de exploit de primer nivel.
Concepto erróneo: La composición de múltiples instrucciones del cliente reemplaza la CPI para la custodia de PDAs.
Si solo el programa puede firmar como la bóveda, el cliente no puede enviar una transferencia de token independiente como esa bóveda. Se requiere CPI con semillas.
Concepto erróneo: Mapear cada error de CPI a un único SomethingFailed está bien.
Los prototipos pueden. Los clientes de producción necesitan códigos específicos y estables para reintentos y respuesta a incidentes.
Concepto erróneo: declare_program! congela la seguridad para siempre.
Las actualizaciones del llamado cambian el comportamiento bajo el mismo ID de programa. Reaudita después de las actualizaciones, incluso cuando la IDL todavía se analiza.
Una llamada tipada a otro programa construida con CpiContext (y generalmente un módulo auxiliar) que se compila a invoke o invoke_signed en tiempo de ejecución.
¿Cuándo uso CpiContext::new en lugar de new_with_signer?
Usa new cuando todos los firmantes requeridos ya firmaron la transacción externa. Usa new_with_signer (o .with_signer) cuando una PDA de tu programa deba autorizar la instrucción interna.
¿De dónde provienen las semillas de firmante?
Los mismos componentes de semillas utilizados para derivar la PDA, más el bump (a menudo de ctx.bumps). Deben coincidir exactamente con la derivación en cadena.
¿Cómo funcionan las transferencias de Token SPL desde un programa?
Incluye las cuentas de token, la mint y Interface<TokenInterface> (o Program<Token> para solo clásico), construye una estructura de cuenta TransferChecked y llama a token_interface::transfer_checked con un CpiContext, pasando amount y los decimals de la mint. Las autoridades PDA necesitan semillas de firmante.
¿Para qué sirve declare_program!?
Genera clientes CPI en tiempo de compilación a partir de la IDL de un programa externo para que obtengas estructuras de cuentas tipadas y ayudantes de instrucción en lugar de discriminadores construidos manualmente.
¿Cómo se relacionan las cuentas restantes con la IDL?
Las cuentas fijas viven en la estructura de cuentas y la IDL. Las cuentas adicionales que los clientes agregan después de esa lista se convierten en remaining_accounts y deben ser validadas y ordenadas según las reglas del llamado.
¿Puede una CPI fallar mientras mis escrituras de estado anteriores permanecen?
Si la instrucción externa devuelve Err después de una CPI, la instrucción falla como una unidad. Estructura el código para que no dependas del éxito parcial cuando la ruta externa aún pueda fallar.
¿Por qué anclar Program<T> en lugar de UncheckedAccount para los programas?
Program<T> verifica que la cuenta sea el ID ejecutable esperado (y ejecutable). Las cuentas de programa no verificadas invitan a ataques de sustitución.
¿Qué tan profundo pueden anidarse las CPIs?
La MAX_INSTRUCTION_STACK_DEPTH es 5. La instrucción de nivel superior ocupa la altura 1, por lo que obtienes 4 niveles de CPI anidada debajo de ella. Solo el anidamiento consume profundidad: un manejador que llama a cinco programas diferentes uno tras otro son cinco CPIs todas a la altura 2 y nunca se acercan al límite. De todos modos, diseña para árboles poco profundos: los gráficos dinámicos profundos son difíciles de razonar para CU y para la frescura del estado posterior a la CPI.
¿Necesito CPI para leer la cuenta de otro programa?
No. Leer datos de cuenta que se te pasaron no requiere CPI. La CPI es para ejecutar la lógica de instrucción de otro programa.
¿Cómo deben los clientes preparar las cuentas para instrucciones con muchas CPIs?
Lista cada cuenta que el programa externo y todos los llamados anidados necesiten, con las banderas correctas de firmante/escribible, coincidiendo con la IDL del programa y la especificación de cuentas restantes, utilizando @solana/kit 7.0.0 o herramientas de cliente Anchor.