CPIs Explicadas
Una invocación entre programas (CPI) es cómo un programa en cadena pide a otro programa que ejecute una instrucción dentro de la misma transacción. Tu manejador construye una Instruction (id del programa, metadatos de cuenta, datos), pasa una porción coincidente de valores AccountInfo, y el runtime carga el llamado y lo ejecuta antes de que el control regrese a ti.
Las CPIs son el mecanismo detrás de la composabilidad de Solana: bóvedas que mueven SOL, custodias que transfieren tokens SPL, enrutadores que llaman a DEXes, y cualquier flujo donde las reglas de propiedad te obligan a pasar por otro programa en lugar de escribir sus cuentas tú mismo.
Esta página es el paraguas para Invocaciones entre Programas. Las páginas hermanas profundizan en los conceptos básicos, el paso de cuentas, invoke vs invoke_signed, objetivos del Sistema y de Tokens, límites de profundidad, reentrada y patrones. Aquí obtienes un modelo coherente para que esas páginas encajen como zooms, no como historias separadas.
Resumen
- Una CPI es una llamada anidada a otro programa: el llamador proporciona datos de instrucción y una lista de cuentas con privilegios restringidos; el llamado se ejecuta bajo los mismos bloqueos de transacción, firmas y presupuesto de cómputo.
- Por Qué Importa: No puedes reescribir cuentas que pertenecen a otro programa. Transferencias, acuñaciones, creación de cuentas y la mayoría de las integraciones de protocolos requieren CPI al programa propietario. Cuentas incorrectas, ruta de firma incorrecta u orden inseguro producen transacciones fallidas o errores de seguridad.
- Conceptos Clave: Instruction, AccountMeta / AccountInfo, propagación de privilegios,
invoke,invoke_signed, semillas PDA, profundidad de CPI, CPI del Programa del Sistema, CPI de Tokens SPL, checks-effects-interactions (comprobaciones-efectos-interacciones), destinatarios en lista blanca. - Cuándo Usar: Cualquier vez que tu programa deba crear cuentas, mover SOL o tokens, acuñar/quemar bajo una autoridad de programa, o componer con otro protocolo en cadena en una transacción atómica.
- Limitaciones / Compensaciones: La profundidad anidada es finita, la CU (Compute Units) se comparte, cada cuenta debe listarse por adelantado, y los IDs de programa de destinatarios arbitrarios son una superficie de confianza. El éxito atómico de múltiples programas es poderoso; el fallo aborta toda la transacción.
- Temas Relacionados: Conceptos Básicos de CPI, invoke vs invoke_signed, Programas del Sistema y de Tokens, límites de profundidad, reentrada, patrones de composabilidad.
Fundamentos
Los programas de Solana son ejecutables sin estado. Los saldos duraderos y los diseños viven en cuentas propiedad de programas específicos. La regla de "solo el propietario escribe" es absoluta: solo el Programa de Tokens puede reescribir los datos de una cuenta de token; solo el Programa del Sistema realiza el ciclo de vida habitual de creación/asignación/transferencia para cuentas propiedad del sistema; solo tu programa puede reescribir cuentas que posee.
Cuando tu lógica necesita que el estado propiedad de otra persona cambie, no manipulas bytes. Haces una CPI al propietario con la instrucción que ese programa define.
A nivel de API, una CPI se parece a construir la misma forma que un cliente enviaría como instrucción de nivel superior:
Transacción Externa
+-- Instrucción: TuPrograma
| cuentas: [usuario, bóveda, token_src, token_dst, token_program, system, ...]
| datos: tu discriminador + argumentos
|
| (dentro de TuPrograma)
| invoke / invoke_signed
| |
| v
+-- Instrucción Anidada: TokenProgram (o System, DEX, ...)
cuentas: subconjunto de la lista externa (orden + indicadores correctos)
datos: transfer / mint / create_account / ...Tres hechos se derivan de esa imagen:
- Misma transacción, misma atomicidad. Si el llamado falla, tu instrucción falla y toda la transacción se revierte.
- Sin descubrimiento ambiental. Cada cuenta que la llamada anidada necesita debe estar ya en la lista de cuentas de la transacción externa (con privilegios de firma/escritura utilizables). Las CPIs no obtienen claves no declaradas a mitad de camino.
- Los privilegios no crecen mágicamente. Las capacidades de escritura y firma en la CPI están limitadas por lo que el mensaje externo ya autorizó, más las firmas de PDA que tu programa pueda añadir con
invoke_signed.
Los clientes (incluidos los creados con @solana/kit 7.0.0) todavía ensamblan el mensaje de nivel superior: listan programas y cuentas, firman con billeteras y establecen el presupuesto de cómputo. Tu programa luego une llamadas anidadas desde ese mundo cerrado de cuentas.
Mecánicas e Interacciones
Construcción de la instrucción anidada
Una CPI utiliza solana_program::instruction::Instruction:
| Campo | Rol |
|---|---|
program_id | Clave pública del programa llamado |
accounts | Vec<AccountMeta>: clave pública, is_signer, is_writable |
data | Bytes opacos que el llamado deserializa (código de operación + argumentos) |
Pasas esa instrucción a invoke o invoke_signed con una porción de clones de AccountInfo cuyo orden y claves coincidan con los metadatos. Prefiere los ayudantes oficiales (system_instruction::transfer, spl_token::instruction::transfer) para que el diseño de datos y el orden de los metadatos se mantengan correctos.
Paso de cuentas y reglas de privilegios
Las listas de cuentas para las CPIs son una superficie de seguridad, no solo contabilidad.
- Orden debe coincidir con lo que el llamado espera (los ayudantes lo codifican; los programas personalizados lo documentan).
- Writable debe ser de mínimo privilegio: marca como escribibles solo las cuentas que el llamado debe mutar.
- Signer en un metadato significa que la cuenta debe ser un firmante reconocido para esa llamada anidada: ya sea una billetera que firmó la transacción externa, o una PDA que tu programa firma a través de semillas.
- Las cuentas de programa (Token, System, o un programa asociado) son generalmente entradas de solo lectura para que el runtime pueda cargar el llamado.
Los errores de escalada de privilegios se parecen a marcar una cuenta como escribible o firmante en la CPI cuando la transacción externa nunca tuvo la intención de esa capacidad. Valida los indicadores de AccountInfo externa antes de promocionarlos a metadatos anidados. Consulta Pasando Cuentas a CPIs para la lista de verificación operativa.
invoke vs invoke_signed
invoke | invoke_signed | |
|---|---|---|
| Reenvía firmas de la transacción externa | Sí | Sí |
| Añade "firmas" de PDA a partir de semillas | No | Sí |
| Uso típico | Autoridad firmada por el usuario ya presente | PDA de bóveda, PDA de autoridad de acuñación, custodia propiedad del programa |
| Modo de fallo si se usa incorrectamente | MissingRequiredSignature cuando la PDA debe autorizar | Semillas/bump incorrectos → falla la verificación de firma |
// La billetera (u otro firmante externo) ya es is_signer en la cuenta
invoke(&ix, &[from.clone(), to.clone(), authority.clone(), token_program.clone()])?;
// Autoridad PDA: el programa prueba semillas + bump bajo su id de programa
invoke_signed(
&ix,
&[vault.clone(), to.clone(), system_program.clone()],
&[&[b"vault", user.key.as_ref(), &[bump]]],
)?;invoke_signed no inventa firmas de billetera. Solo permite que el programa actual autorice PDAs derivadas de su ID de programa y las porciones de semillas proporcionadas. El diseño de semillas y la disciplina de bump viven con las PDAs; la elección de CPI es simplemente "¿necesita el llamado una PDA como firmante?". Comparación completa: invoke vs invoke_signed.
Límites de profundidad y cómputo
El runtime limita qué tan profundas pueden ser las CPIs anidadas. En la Solana actual (incluyendo Agave 4.1.1), la profundidad máxima de CPI es 4. Árboles profundos (router → DEX → token → hook de transferencia → …) alcanzan el límite rápidamente.
Las unidades de cómputo son por transacción. Las llamadas anidadas no obtienen una billetera de CU fresca; los llamados pesados y el registro multiplican el costo bajo el presupuesto predeterminado de ~1.4M CU (ajustable a través de instrucciones de Presupuesto de Cómputo dentro de los límites del protocolo). Diseña de forma plana cuando puedas: una instrucción bien formada con algunas CPIs es mejor que una cadena de llamadas profunda. Detalles: Profundidad y Límites de CPI.
Objetivos cotidianos: Sistema y Token SPL
Las CPIs del Programa del Sistema crean cuentas, asignan espacio, asignan propietarios y transfieren lamports. El flujo típico para una nueva cuenta propiedad del programa: mínimo exento de alquiler de Rent::get(), luego create_account (o patrones de asignación/asignación) con el pagador como firmante. El movimiento de SOL financiado por PDA o de origen PDA necesita invoke_signed. Ver CPI al Programa del Sistema.
Las CPIs de Tokens SPL acuñan, transfieren, queman, aprueban y operaciones relacionadas. Nunca reescribes los bytes de la cuenta de token tú mismo; construyes una instrucción con spl_token::instruction (o equivalentes de Token-2022) e invoke / invoke_signed con el ID de programa de token correcto, acuñación, autoridades y cuentas de token. Valida que la acuñación coincida, los decimales (cantidades brutas), el estado de congelación y el ID del programa Token vs Token-2022. Las autoridades de acuñación o congelación de PDA usan invoke_signed. Ver CPI a Tokens SPL.
Reentrada y seguridad
A diferencia de los entornos con un mutex global predeterminado, Solana no te da un bloqueo de reentrada gratuito. Otro programa al que haces CPI puede hacer CPI de vuelta a ti (hooks de transferencia, callbacks, IDs de programa maliciosos proporcionados por el usuario). Trata las CPIs externas como efectos secundarios no confiables.
Reglas prácticas:
- Comprobaciones → efectos → interacciones: valida, actualiza y serializa tu estado, luego haz la CPI externa.
- Indicadores idempotentes: una vez que un retiro o liquidación se marca como hecho, la reentrada no puede pagar dos veces.
- Lista blanca de destinatarios cuando el ID del programa no es una constante fija (enrutadores, plugins).
- No asumas una única entrada si los hooks de Token-2022 o los programas asociados pueden llamarte a mitad de camino.
Los límites de profundidad limitan la pila; no hacen que el orden inseguro sea seguro. Ver Reentrada y Seguridad.
Consideraciones Avanzadas y Aplicaciones
La composabilidad es el producto de las CPIs realizadas cuidadosamente: una transacción puede validar un usuario, actualizar un libro mayor, intercambiar en un DEX y depositar en una bóveda, todo o nada.
| Patrón | Qué hace | Forma de CPI | Ten cuidado con |
|---|---|---|---|
| Custodia / bóveda | El programa custodia activos bajo una PDA | invoke_signed al Sistema o Token | Semillas, bumps, campos de autoridad |
| Enrutador / agregador | Una instrucción de entrada se ramifica a socios | Múltiples invokes, IDs de programa en lista blanca | Profundidad, CU, slippage, cuentas restantes |
| Adaptador | Normaliza un protocolo externo detrás de tu API | Envoltura delgada de CPI + tu estado | Actualizaciones de socios y deriva del diseño de cuentas |
| Multi-instrucción compuesta por cliente | Sin CPI anidada; varias instrucciones de nivel superior | Cero o pocas CPIs en tu programa | Orquestación de programa único más débil; sigue siendo atómica como una tx |
Las cuentas restantes permiten a los clientes pasar colas de cuentas extensibles (pools adicionales, oráculos, programas de memo) sin codificar en exceso cada ranura en tu estructura Accounts. Documenta el diseño por versión y valida cada clave y propietario antes de la CPI.
Las Tablas de Búsqueda de Direcciones (ALT) abordan el tamaño de la transacción, no la profundidad de la CPI. Ayudan cuando las listas de cuentas explotan; no elevan el límite de profundidad 4.
Anchor 0.32.1 genera ayudantes de CPI tipados (CpiContext, envoltorios invoke_signed, anchor_spl) para que los metadatos y discriminadores se mantengan alineados con los IDL. El modelo de runtime no cambia: mismos privilegios, misma profundidad, misma necesidad de ordenamiento CEI. Rust nativo con solana_program es la misma superficie de syscall con más cableado manual.
Para revisiones de arquitectura, pregunta: ¿qué programa posee cada cuenta que tocamos; qué firmantes (billetera o PDA) autorizan cada llamada anidada; si los IDs de programa de destinatarios son fijos o están en lista blanca; si el estado se finaliza antes de la CPI externa; si el árbol de llamadas encaja en la profundidad y CU bajo simulación realista con herramientas Agave 4.1.1 / CLI 3.0.10.
Conceptos Erróneos Comunes
- "CPI es solo una llamada API interna estilo HTTP." Es una instrucción anidada mediada por el runtime bajo los mismos bloqueos, firmas y presupuesto de CU, con reglas estrictas de cuentas y profundidad.
- "Mi programa puede escribir saldos de tokens si paso la cuenta de token." Solo el Programa de Tokens posee esos bytes. Debes hacer una CPI con una instrucción de transferencia/acuñación/quema válida y autoridad.
- "
invoke_signedme permite falsificar cualquier firma." Solo autoriza PDAs para tu ID de programa con las semillas que proporcionas. No crea firmas de billetera. - "Si conozco una clave pública, la CPI puede cargarla." Las cuentas no declaradas no están disponibles. Los clientes deben incluir cada cuenta que el árbol de llamadas completo necesita.
- "Los programas anidados obtienen cada uno su propio presupuesto de cómputo." Todo el trabajo anidado se extrae del presupuesto de la transacción.
- "La reentrada es un problema solo de EVM." Los hooks y callbacks lo hacen real en Solana; ordena las actualizaciones de estado antes de la CPI externa y haz una lista blanca de objetivos no confiables.
- "Una composición más profunda siempre es mejor." Los límites de profundidad 4 y CU castigan los árboles profundos; los diseños más planos y las transacciones de múltiples instrucciones del lado del cliente suelen ser más saludables.
- "Marcar todo como escribible es más seguro." Amplía el radio de explosión si un llamado o un error muta más de lo previsto. Usa el mínimo privilegio.
Preguntas Frecuentes
¿Qué es una CPI en una oración?
Una invocación entre programas es tu programa ejecutando la instrucción de otro programa a mitad de su manejador, dentro de la misma transacción atómica, con una lista de cuentas declarada y un presupuesto de cómputo compartido.
¿Por qué no puedo simplemente editar los datos de cuenta de otro programa?
El runtime impone la regla de "solo el propietario escribe": solo el programa propietario puede mutar los datos de una cuenta. La CPI es cómo pides a ese propietario que aplique una instrucción soportada.
¿Qué debe ser cierto de las cuentas que paso a una CPI?
Deben aparecer ya en la transacción externa, coincidir con el orden y roles esperados por el llamado, y tener privilegios de firma/escritura que el mensaje externo (más cualquier firma de PDA) permita legítimamente.
¿Cuándo uso invoke vs invoke_signed?
Usa invoke cuando todos los firmantes requeridos ya firmaron la transacción. Usa invoke_signed cuando una PDA derivada de tu programa deba actuar como firmante (bóvedas, autoridades de acuñación, custodias propiedad del programa).
¿Cuál es el límite de profundidad de CPI?
La profundidad máxima anidada de CPI es 4 en la Solana actual (Agave 4.1.1). Planifica los gráficos de llamadas para que los programas de Tokens, hooks y asociados no agoten la pila.
¿Las CPIs obtienen presupuestos de unidades de cómputo separados?
No. La ejecución anidada consume el mismo grupo de CU de la transacción. Simula rutas con múltiples CPIs y establece instrucciones de Presupuesto de Cómputo cuando sea necesario.
¿Cómo encajan las CPIs del Programa del Sistema en el trabajo diario del programa?
Crean y financian cuentas y mueven SOL. create_account exento de alquiler y transfer son los bloques de construcción comunes; las fuentes PDA necesitan invoke_signed.
¿Cómo encajan las CPIs de Tokens SPL en el trabajo diario del programa?
Las operaciones de acuñación, transferencia, quema y autoridad pasan por Token o Token-2022 a través de constructores de instrucciones y CPI. Valida el ID del programa, la acuñación, las cantidades y las autoridades antes de llamar.
¿Cuál es el error de seguridad de CPI más común?
Actualizar saldos o indicadores después de una CPI externa (o llamar a IDs de programa no listados en lista blanca), lo que permite que la reentrada o los llamados maliciosos observen un estado inconsistente.
¿Puede un llamado volver a llamar a mi programa?
Sí, dentro de los límites de profundidad. Diseña manejadores para que sean seguros si se vuelven a entrar: finaliza el estado primero, usa indicadores establecidos y trata los hooks como rutas de primera clase.
¿Anchor elimina la necesidad de entender las CPIs?
No. Anchor 0.32.1 mejora la tipificación y la ergonomía; todavía eliges invoke vs. semillas firmadas, pasas cuentas correctamente, respetas la profundidad/CU y ordenas las actualizaciones de estado de forma segura.
¿Cómo deben prepararse los clientes para una instrucción intensiva en CPI?
Incluye cada cuenta que necesita la ruta anidada completa (incluidos programas y sysvars), establece un presupuesto de cómputo adecuado y prefiere las ALT cuando el recuento de cuentas infla el tamaño del mensaje.
¿Es la composición de múltiples instrucciones sin CPI una alternativa válida?
Sí. Varias instrucciones de nivel superior en una transacción permanecen atómicas sin profundidad anidada. Úsala cuando no necesites lógica intermedia impuesta por el programa entre pasos.
¿Adónde debo ir después de esta página?
Comienza con Conceptos Básicos de CPI y invoke vs invoke_signed, luego los objetivos del Sistema/Token, profundidad, reentrada y Patrones de Composabilidad a medida que diseñas flujos reales.
Relacionado
- Conceptos Básicos de CPI - primeros patrones de
invokey construcción de Instrucción - invoke vs invoke_signed - firmas de billetera vs firma de semillas de PDA
- CPI al Programa del Sistema - crear cuentas y transferir SOL
- CPI a Tokens SPL - acuñar, transferir y quemar a través del programa de Tokens
- Profundidad y Límites de CPI - límite de profundidad, presión de CU, aplanamiento de diseños
- Patrones de Composabilidad - enrutadores, adaptadores y flujos de trabajo de múltiples programas
Versiones de la Pila: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, y @solana/kit 7.0.0.