Instrucciones y Contexto
En Anchor, una instrucción es la unidad de trabajo en la cadena: una función pública dentro de #[program], una estructura de cuentas coincidente, argumentos tipados opcionales y un Result que indica al runtime si la ruta de la transacción puede continuar. Los clientes (incluidos los creados con @solana/kit 7.0.0) no llaman a tus funciones Rust por nombre de forma aislada. Envían un ID de programa, un discriminador de 8 bytes, argumentos codificados en Borsh y una lista cerrada de cuentas. Anchor deserializa ese mensaje, valida cada cuenta contra tus restricciones #[derive(Accounts)], y luego entra en tu manejador con un Context<T> tipado.
Esta página es el paraguas para Instrucciones y Contexto. Las páginas hermanas profundizan en los manejadores, argumentos, acceso a cuentas, ranuras de los programas System y Token, valores de retorno y CPI, y el ordenamiento de múltiples instrucciones. Aquí obtienes un modelo coherente para que esas páginas encajen como zooms, no como historias separadas.
Resumen
- Una instrucción de Anchor es un manejador más un
Contextvalidado: las restricciones se ejecutan primero, luego tu código lee o mutactx.accounts, usa los argumentos de la instrucción y opcionalmente hace CPI o escribe resultados que los clientes pueden observar. - Por Qué Importa: Casi todos los errores de seguridad e integración en los programas de Anchor comienzan aquí: orden incorrecto de cuentas, falta de
mut, argumentos confiados sin firmantes, cuentas de programa System/Token omitidas, o manejadores que asumen init previo sin forzarlo. - Conceptos Clave: Manejador
#[program],Context<T>,#[derive(Accounts)], argumentos de instrucción (Borsh),ctx.accounts/ctx.bumps/remaining_accounts,Program<System>/Program<Token>, CPI desde manejadores, ordenamiento de instrucciones, clientes impulsados por IDL. - Cuándo Usar: Diseñando o revisando cualquier superficie de programa de Anchor 0.32.1: nuevas instrucciones, constructores de clientes, flujos de usuario de varios pasos, o auditorías de cómo interactúan las cuentas y los argumentos.
- Limitaciones / Compensaciones: La validación del contexto cuesta cómputo; cargas útiles de argumentos grandes y
remaining_accountsilimitados son superficies de ataque; los CPI comparten el presupuesto de CU de la transacción y el límite de profundidad; los flujos de múltiples instrucciones son atómicos solo dentro de una transacción. - Temas Relacionados: Manejadores de Instrucciones, Argumentos de Instrucción, Accediendo a Cuentas, Los Programas System y Token, Valores de Retorno y CPI desde Manejadores, Ordenamiento de Instrucciones y Dependencias.
Fundamentos
Los programas de Solana son ejecutables. El estado vive en las cuentas. Un cliente construye una transacción con una o más instrucciones; cada instrucción nombra un programa, bytes de datos y metadatos de cuenta (pubkey, firmante, escribible). El runtime carga tu programa SBF, pasa la lista de cuentas y los datos, y mide las unidades de cómputo bajo las reglas de Agave 4.1.1.
Anchor se basa en ese modelo y estandariza tres capas:
- Despacho -
#[program]exporta manejadores con nombre; la IDL y los discriminadores de 8 bytes mapean los métodos del cliente a esas funciones. - Validación - Para cada manejador, una estructura de cuentas con restricciones (
Signer,mut,seeds,init, verificaciones de tokens, etc.) se ejecuta antes de la lógica de negocio. - Cuerpo del manejador - Tu código Rust recibe
Context<T>(y argumentos) con las cuentas ya tipadas y verificadas, luego devuelveResult<()>, o otro tipoResultcuando devuelves datos intencionalmente.
Cliente (Kit / Anchor TS / Codama)
|
v
Instrucción: program_id + discriminador + argumentos Borsh
Metadatos de cuenta en orden IDL (+ remaining_accounts opcionales)
|
v
Runtime carga el programa (.so)
|
v
Anchor: coincide el discriminador
-> construye Context (restricciones fallan => Err)
-> deserializa argumentos
-> llama a handler(ctx, args...)
|
+--> muta campos de Account<'info, T>
+--> CPI (System, Token, programas asociados)
+--> emit! / set_return_data (opcional)
|
v
Ok => serializa cuentas sucias; Err => toda la instrucción fallaLos manejadores son funciones públicas ordinarias. Convención: el primer parámetro es siempre Context<AccountsStruct>, seguido de cero o más argumentos que coinciden con el orden de la IDL. Mantenlos delgados: valida las reglas de negocio con require!, actualiza el estado, llama a ayudantes o módulos CPI, y devuelve.
El Contexto no es una bolsa libre de cuentas. Después de una evaluación exitosa de restricciones, expone:
| Campo | Rol |
|---|---|
ctx.accounts | Campos tipados de tu estructura de cuentas |
ctx.program_id | La pubkey de este programa (declare_id!) |
ctx.bumps | Bumps canónicos para cuentas que usaron seeds + bump |
ctx.remaining_accounts | AccountInfo adicionales después de la lista fija de la IDL |
Argumentos vs. cuentas. Los argumentos son datos puros (cantidades, enums, ids, pubkeys opcionales). Las cuentas son identidades en la cadena con propietarios, lamports, datos y flags de firmante/escribible que el runtime aplica. Prefiere cuentas cuando una clave debe firmar, contener estado o estar restringida por seeds. Prefiere argumentos para escalares y configuración pequeña que ya no esté en la cadena. Nunca trates una pubkey de argumento como autoridad autenticada a menos que una Signer coincidente (o firma CPI de PDA) lo pruebe.
System y Token en el contexto. Crear cuentas, pagar alquiler, transferir SOL y mover saldos SPL requiere CPI en el programa System o Token (o Token-2022). Anchor lo hace seguro requiriendo cuentas de programa tipadas como Program<'info, System> y Program<'info, Token> (o interfaces de token) para que un cliente no pueda sustituir un ejecutable malicioso. Las restricciones init y de cuenta de token asociada también extraen los programas System y de token a la lista de cuentas automáticamente en espíritu: todavía los declaras para que la transacción pueda pasarlos y las restricciones puedan verificar sus IDs.
Mecánicas e Interacciones
Manejadores de instrucciones y Context<T>
use anchor_lang::prelude::*;
#[program]
pub mod vault {
use super::*;
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
require!(amount > 0, VaultError::InvalidAmount);
let vault = &mut ctx.accounts.vault;
vault.total = vault
.total
.checked_add(amount)
.ok_or(VaultError::Overflow)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut, seeds = [b"vault", owner.key().as_ref()], bump = vault.bump)]
pub vault: Account<'info, VaultState>,
pub owner: Signer<'info>,
}Flujo para cada llamada: se ejecutan las restricciones en Deposit (el propietario firmó, la PDA de la bóveda coincide con las seeds, la bóveda es escribible y propiedad del programa), se registran los bumps si se derivaron, y luego se ejecuta deposit. Los fallos en las restricciones nunca llegan a la lógica de negocio. Ese orden de fallo cerrado es la principal ventaja de seguridad de Anchor 0.32.1 sobre el despacho hecho a mano.
Argumentos de instrucción
Después de Context, los parámetros se decodifican Borsh desde los datos de la instrucción (después del discriminador). El orden y los tipos deben coincidir con el cliente y la IDL. Usa #[instruction(...)] en la estructura de cuentas cuando las restricciones necesiten valores de argumento (por ejemplo, seeds de PDA que incluyen un ID):
#[derive(Accounts)]
#[instruction(id: u64)]
pub struct CreatePool<'info> {
#[account(
init,
payer = payer,
space = 8 + Pool::INIT_SPACE,
seeds = [b"pool", id.to_le_bytes().as_ref()],
bump,
)]
pub pool: Account<'info, Pool>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}Valida rangos temprano (fee_bps <= 10_000, montos no cero). Mantén las superficies de argumentos pequeñas: Vecs grandes agotan la CU y el tamaño del mensaje. Genera clientes desde la IDL para que el orden de serialización nunca se desvíe.
Accediendo a cuentas dentro del manejador
&ctx.accounts.field/&mut- Lee o escribe estado tipado.Account<'info, T>se reserializa al soltarla si es mutable y está sucia.ctx.bumps.field_name- Usa el mismo nombre de campo que la cuenta de seeds; pasa aCpiContext::new_with_signersin volver a buscar bumps.ctx.remaining_accounts- Colas dinámicas (enrutadores, oráculos opcionales). Documenta el orden, valida el propietario, la escribibilidad y los discriminadores tú mismo; la IDL no los describe completamente.to_account_info()- Convierte envoltorios tipados en listas de cuentas CPI.
El mínimo privilegio aún se aplica: solo marca las cuentas como mut cuando la instrucción deba escribirlas. Solo lectura cuando sea posible reduce el radio de explosión si un error o un objetivo CPI se comporta mal.
Programas System y Token en el contexto
Declara cuentas de programa para cada instrucción que crea cuentas, mueve SOL o toca saldos SPL:
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
// Token-2022 / soporte dual a menudo usa Interface<'info, TokenInterface>Program<T> verifica el ID del ejecutable. Omitirlo (o usar infos sin procesar y sin verificar) invita a la sustitución de programas. El Programa de Cuenta Asociada aparece cuando creas ATAs. Las rutas de Token-2022 necesitan el ID de programa correcto y, a menudo, tipos de interfaz para que tanto Token clásico como Token-2022 puedan ser aceptados de forma segura. Los detalles y las tablas se encuentran en Los Programas System y Token.
Valores de retorno y CPIs desde manejadores
La mayoría de los manejadores devuelven Result<()> y comunican resultados escribiendo cuentas y emitiendo eventos (emit!). Solana también admite datos de retorno de instrucciones; úsalos con moderación para resultados síncronos pequeños para un llamador CPI. Los indexadores y las dApps casi siempre prefieren eventos o estado de cuenta sobre búferes de retorno opacos.
Cuando el manejador debe mover SOL o tokens, o llamar a otro programa, construye un CPI con las cuentas ya presentes en el contexto:
use anchor_lang::system_program::{transfer, Transfer};
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,
)?;Las autoridades PDA usan CpiContext::new_with_signer y ctx.bumps. Los errores de CPI se propagan con ?. La profundidad anidada y la CU son preocupaciones de toda la transacción (límite de profundidad 4 en los clústeres actuales); aplana cuando sea posible. Ver Valores de Retorno y CPI desde Manejadores.
Ordenamiento y dependencias entre instrucciones
Un manejador es una instrucción. Los clientes a menudo encadenan varios: crear PDA de usuario, crear ATA, luego depositar. Dentro de una sola transacción, las instrucciones posteriores ven el estado de la cuenta escrito por las anteriores. Entre transacciones, nada es atómico: diseña pasos idempotentes o flags de estado explícitos, y devuelve errores claros cuando faltan prerrequisitos (NotInitialized) en lugar de fallar.
Misma transacción (atómica)
1. create_user -> init PDA
2. create_ata -> init cuenta de token
3. deposit -> transferir + actualizar bóveda
Transacciones separadas (posible finalización parcial)
tx A: create_user
tx B: deposit // debe tolerar o detectar la configuración faltanteElige una instrucción "gorda" cuando el programa deba forzar invariantes intermedios sin confiar en el cliente. Elige varias instrucciones pequeñas cuando los límites de CU, la componibilidad o las reintentos de UX favorezcan pasos delgados. Documenta el orden requerido en la documentación y los comentarios de la IDL para que los constructores de Kit no inviertan init y uso.
Consideraciones Avanzadas y Aplicaciones
Manejadores de propósito único. Separa las superficies de administración, usuario y liquidación. Los manejadores pequeños componen mejor en transacciones múltiples, mantienen predecibles los presupuestos de CU y simplifican las auditorías. La lógica compartida pertenece a módulos, no copiada y pegada entre funciones #[program].
Argumentos en seeds vs. cuentas en seeds. Las identidades estables (billetera del usuario, mint) suelen provenir de campos de cuenta. Los IDs numéricos o de cadena a menudo provienen de argumentos a través de #[instruction]. Mezclar ambos es normal; cambiar cualquiera de ellos cambia la PDA. Almacena el bump en init y restringe bump = account.bump después para evitar bumps no canónicos.
Grafos de cuentas dinámicos. Los enrutadores y los flujos de salto múltiple envían cuentas adicionales a través de remaining_accounts. Versiona el diseño esperado, limita la longitud y valida cada entrada antes del CPI. Las cuentas fijas en la estructura siguen siendo el predeterminado para las autoridades de alto riesgo.
Generación de clientes. Después de anchor build, la IDL impulsa los pipelines de TypeScript y Kit. El orden de las cuentas en el cliente debe coincidir con la estructura de cuentas. Las cuentas restantes son siempre un segundo contrato: publícalas junto a la IDL. Prefiere Codama o la generación de código de cliente de Anchor sobre los discriminadores empaquetados a mano.
Probar el contrato de una instrucción. Las pruebas unitarias (LiteSVM) y las pruebas de integración (anchor test contra un validador local o Surfpool) deben cubrir fallos de restricciones (firmante incorrecto, seeds incorrectas), validación de argumentos, mutabilidad de cuentas CPI y rutas felices de múltiples instrucciones. Simula la CU en manejadores "calientes" bajo las herramientas de Solana CLI 3.0.10 para que las instrucciones del presupuesto de cómputo de producción sean realistas.
Lista de verificación de revisión de seguridad para esta capa. Para cada instrucción, pregunta: ¿qué cuentas son firmantes? ¿cuáles son escribibles y por qué? ¿qué PDAs se derivan y dónde viven los bumps? ¿si los IDs de los programas System/Token están tipados? ¿si los argumentos se autentican o son meros parámetros? ¿si el estado se finaliza antes del CPI externo? ¿si los clientes pueden reintentar de forma segura?
Conceptos Erróneos Comunes
- "Contexto es solo una bolsa de AccountInfos." Las restricciones ya se ejecutaron. Trata
ctx.accountscomo validado; no abras rutas sin verificar de nuevo sin razón. - "Si el cliente pasa la pubkey correcta en los argumentos, el firmante está probado." Solo las cuentas
Signer(oinvoke_signedde PDA) prueban la autoridad. Los argumentos pueden mentir. - "
initsignifica que puedo omitir el programa system." La creación todavía pasa por las verificaciones del Programa System; declaraProgram<System>y un pagador mutable. - "Las transferencias de tokens se pueden hacer escribiendo la cuenta de token en mi programa." Solo Token/Token-2022 puede reescribir los datos de la cuenta de token. Haz CPI con una autoridad válida.
- "Los valores de retorno del manejador son cómo el frontend obtiene resultados." Prefiere el estado de cuenta y los eventos. Los datos de retorno son nicho y de tamaño limitado.
- "El orden de instrucciones en el cliente es cosmético." Init-antes-de-uso y los flags de mutabilidad hacen que el orden sea fundamental en transacciones de múltiples instrucciones.
- "
remaining_accountses seguro porque Anchor tipó el resto." Los extras no se verifican por defecto. Valida propietarios y roles tú mismo. - "Una instrucción gigante es siempre más segura." Puede forzar la atomicidad, pero también dispara la CU y acopla fallos no relacionados. Adapta la forma de la instrucción al riesgo del producto.
Preguntas Frecuentes
¿Qué es una instrucción de Anchor en una oración?
Una función pública #[program] cuyas cuentas se validan en un Context, cuyos argumentos se decodifican Borsh desde los datos de la instrucción, y cuyo Result decide el éxito o fracaso de ese paso de la transacción.
¿Qué se ejecuta primero: el cuerpo de mi manejador o las restricciones de cuenta?
Las restricciones en la estructura de cuentas siempre se ejecutan primero. Si fallan, el manejador nunca se ejecuta.
¿Qué contiene Context?
Cuentas tipadas (ctx.accounts), el ID de este programa, bumps de PDA de las restricciones de seeds (ctx.bumps), y cualquier remaining_accounts final que el cliente haya añadido.
¿Cómo debería elegir entre un argumento y una cuenta?
Usa cuentas para claves que deban firmar, contener estado o estar restringidas por seeds. Usa argumentos para escalares pequeños y configuración que aún no esté representada en la cadena.
¿Por qué System y Token aparecen en tantas estructuras de cuentas?
init, movimiento de SOL y operaciones SPL hacen CPI en esos programas. Las cuentas Program tipadas fijan los IDs de ejecutables correctos para que los clientes no puedan reemplazar un programa falso.
¿Cuándo debería un manejador hacer CPI en lugar de depender de otra instrucción de nivel superior?
CPI cuando tu programa debe forzar lógica intermedia y cambios de estado anidados atómicamente bajo su propio control. Usa múltiples instrucciones de nivel superior cuando los pasos sean independientes, consuman mucha CU, o sea mejor reintentarlos por separado dentro de una transacción construida por el cliente.
¿Cómo saben los clientes el orden de las cuentas y los tipos de argumentos?
Desde la IDL de Anchor producida por anchor build, consumida por Anchor TS, Codama, o constructores escritos a mano de @solana/kit 7.0.0 que coinciden con el mismo diseño.
¿De dónde provienen los bumps usados en la firma CPI?
De ctx.bumps.<field> cuando la estructura de cuentas declaró seeds y bump, o de un campo bump almacenado en la cuenta después de init. No inventes bumps ad hoc en el manejador.
¿Puede una transacción llamar a varias de mis instrucciones?
Sí. Ordénalas para que los creadores se ejecuten antes que los consumidores. Toda la transacción es atómica; el progreso parcial a través de transacciones separadas no lo es.
¿Deberían los manejadores devolver datos enriquecidos a la UI?
Generalmente no. Escribe cuentas de estado y emite eventos para indexadores y UIs. Reserva los datos de retorno para señales pequeñas de CPI a CPI si realmente los necesitas.
¿Qué versiones de Anchor y la cadena de herramientas asume esta sección?
Anchor 0.32.1, Rust 1.91.1, Agave 4.1.1, Solana CLI 3.0.10, y clientes en @solana/kit 7.0.0 a menos que una página indique lo contrario.
¿Qué debería leer a continuación?
Comienza con Manejadores de Instrucciones y Argumentos de Instrucción, luego Accediendo a Cuentas y Los Programas System y Token. Añade Valores de Retorno y CPI desde Manejadores y Ordenamiento de Instrucciones y Dependencias cuando diseñes flujos de múltiples pasos o multi-programa.
Relacionado
- Manejadores de Instrucciones - firmas,
Context<T>, y estilo de manejador delgado - Argumentos de Instrucción - argumentos Borsh,
#[instruction], y validación - Accediendo a Cuentas -
ctx.accounts, bumps, yremaining_accounts - Los Programas System y Token - cuentas de programa requeridas para init y trabajo con tokens
- Valores de Retorno y CPI desde Manejadores - eventos, datos de retorno, y CPI desde manejadores
- Ordenamiento de Instrucciones y Dependencias - flujos de cliente de múltiples instrucciones y prerrequisitos
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.