Errores y Eventos de Anchor
Errores, eventos y registros son la forma en que un programa de Anchor comunica resultados que el estado de la cuenta por sí solo no captura: fallar con un código estable, publicar un registro de éxito tipado para indexadores o dejar un breve rastro humano para los ingenieros. Anchor 0.32.1 integra esos canales en el IDL para que los clientes e indexadores compartan un contrato con el binario en cadena.
#[error_code] y require! es la entrada práctica; Mensajes de Error Personalizados, Eventos con emit!, emit_cpi! y Eventos Self-CPI, Registro y Costo de CU, y Decodificación de Errores del Lado del Cliente profundizan en cada superficie. Esta página es el mapa subyacente.
Resumen
- Anchor divide la observabilidad en errores tipados (
#[error_code],require!,error!), eventos estructurados (#[event],emit!,emit_cpi!), y registros presupuestados (msg!), todos exportables a través del IDL para consumidores fuera de la cadena. - Por Qué Importa: Los usuarios nunca ven tu cadena
?de Rust. Ven fallos de simulación, líneas de registro del explorador y lo que sea que tu dApp mapee desdeCustom(u32). Diseña esas superficies a propósito o enviarás una UX opaca de "Transacción fallida" y desperdicio de CU. - Conceptos Clave:
#[error_code],require!/error!,#[msg], constraint@ Error,#[event]/emit!/emit_cpi!, costo de CU de registro, tablas de errores del IDL, decodificación basada en simulación primero. - Cuándo Usar: Diseñar modos de fallo para bóvedas, mercados o rutas de administración; instrumentar el éxito para análisis; conectar @solana/kit 7.0.0 o clientes de Anchor para decodificar fallos; explicar la observabilidad del programa a los equipos de frontend e indexadores.
- Limitaciones / Compensaciones: Cada registro y evento consume unidades de cómputo; cada código de error es una API pública que debes versionar; las instrucciones fallidas revierten el estado; los registros y eventos son datos públicos de la cadena (sin secretos).
- Temas Relacionados: códigos de error y require, mensajes personalizados, emit y emit_cpi, CU de registro, decodificación del lado del cliente.
Fundamentos
Un manejador de instrucciones de Anchor devuelve Result<()>. El éxito confirma las mutaciones de cuenta; el fallo aborta la instrucción (y la transacción) y revierte esas mutaciones. No hay confirmación parcial de "campos de error". El fallo en sí es la señal.
Ese diseño impulsa la observabilidad a metadatos de ejecución y al IDL, no a cuentas duraderas de last_error (a menos que escribas dicho estado en una ruta de éxito). Tres canales llenan el vacío:
| Canal | Consumidor principal | Semántica de fallo | Superficie de Anchor |
|---|---|---|---|
| Error | Runtime, billeteras, decodificadores de dApp | La instrucción falla; el estado se revierte | #[error_code], require!, error!, constraint @ |
Registro (msg!) | Ingenieros, exploradores, soporte | Visible para pasos ejecutados hasta el fallo | msg!, depuración con función activada |
| Evento | Indexadores, análisis, webhooks | Emitido en rutas que se ejecutan lo suficiente para registrar | #[event], emit!, emit_cpi! |
Manejador de Anchor (Result<()>)
|
+-- require! / error! / ? --> Err(code) --> falla tx + registros hasta ahora
|
+-- msg!("...") --> Costo CU --> logMessages
|
+-- emit! / emit_cpi! --> #[event] --> indexadores / consumidores de IDL
|
+-- Ok(()) --> confirma escrituras de cuentaLos errores son el contrato de flujo de control. Prefiere variantes tipadas para reglas orientadas al usuario y mantén los mensajes seguros para el producto. Los registros son un canal de depuración corto, prefijado y con función activada. Los eventos son el canal estructurado para sistemas fuera de la cadena que no deberían analizar texto libre.
Los tres comparten una propiedad: los registros de transacciones son públicos. Las semillas, las claves privadas, la PII y los internos listos para explotar no pertenecen allí.
La ventaja de Anchor sobre ProgramError::Custom en bruto es la integración con el IDL: anchor build exporta nombres de error, códigos, cadenas #[msg] opcionales y esquemas de eventos en target/idl/*.json. Los clientes fijan ese artefacto al programa desplegado.
Mecánicas e Interacciones
#[error_code], require!, y error!
Define fallos de dominio como un enum. Anchor asigna códigos numéricos estables (convencionalmente comenzando en 6000) y los incluye en el IDL:
#[error_code]
pub enum VaultError {
#[msg("Vault is paused")]
Paused,
#[msg("Insufficient balance")]
InsufficientFunds,
#[msg("Math overflow")]
Overflow,
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
require!(!ctx.accounts.vault.paused, VaultError::Paused);
require!(
ctx.accounts.vault.balance >= amount,
VaultError::InsufficientFunds
);
ctx.accounts.vault.balance = ctx.accounts.vault.balance
.checked_sub(amount)
.ok_or(VaultError::Overflow)?;
Ok(())
}| Macro / forma | Rol |
|---|---|
require!(cond, Error::Variant) | Afirmación booleana; falla con la variante |
require_eq! / require_keys_eq! | Comprobaciones de igualdad / pubkey |
error!(Error::Variant) / err! | Retorno temprano sin una guarda booleana |
? en Result | Propaga o mapea errores de CPI y auxiliares |
Usa restricciones para reglas a nivel de cuenta para que las cuentas inválidas nunca lleguen a la lógica del cuerpo del manejador:
#[account(constraint = !pool.disabled @ SwapError::PoolDisabled)]
pub pool: Account<'info, Pool>,Variantes de solo añadir para que los códigos nunca se remapeen silenciosamente. Reordenar renumerará los códigos y romperá todos los mapas de clientes. Trata el enum como una API pública versionada.
Profundiza: #[error_code] y require!.
Mensajes personalizados con #[msg]
#[msg("...")] adjunta una cadena de texto predeterminada en inglés a cada variante. Esa cadena llega al IDL para exploradores, AnchorError y tablas generadas. No es un sistema de localización ni un lugar para valores dinámicos. Mantén las cadenas cortas, seguras para el usuario y estables.
Fuera de la cadena, mapea códigos a copias de producto, claves i18n y sugerencias de reintento. El inglés en cadena es una solución alternativa para herramientas, no la única superficie de UX. Detalles: Mensajes de Error Personalizados.
Eventos con emit!
Cuando los paneles o los plugins de Geyser necesitan campos confiables, el análisis de msg! de texto libre se convierte en un pasivo. Marca una struct amigable con Borsh con #[event] y publícala en rutas de éxito:
#[event]
pub struct PositionOpened {
pub owner: Pubkey,
pub market: Pubkey,
pub size: u64,
pub timestamp: i64,
}
emit!(PositionOpened {
owner: ctx.accounts.owner.key(),
market: ctx.accounts.market.key(),
size,
timestamp: Clock::get()?.unix_timestamp,
});Los eventos aparecen en el IDL. Los campos deben ser públicos y serializables. Prefiere claves y montos sobre grandes bloques de datos. El estado de la cuenta sigue siendo la autoridad. Las instrucciones fallidas no confirman el estado, así que diseña la UX de fallo en torno a códigos de error, no a eventos de error duraderos. Profundiza: Eventos con emit!.
emit_cpi! y entrega de eventos self-CPI
El emit! clásico registra datos de eventos en un formato que muchas herramientas entienden, a menudo a través de un patrón interno de self-CPI. emit_cpi! hace explícita la ruta CPI para que los indexadores que dependen de la observación de CPI capturen eventos de manera más confiable:
emit_cpi!(LiquidityAdded {
provider: ctx.accounts.provider.key(),
amount,
});| Macro | Costo CU | Ajuste típico |
|---|---|---|
emit! | Menor | Pruebas, pipelines simples, telemetría de menor riesgo |
emit_cpi! | Mayor | Indexadores de producción con requisitos estrictos de captura |
Estandariza un estilo por programa para que los analizadores se mantengan simples. Presupuesta CU después de cambiar a emit_cpi! en rutas activas. Detalles: emit_cpi! y Eventos Self-CPI.
Registro y unidades de cómputo
msg! invoca la llamada al sistema de registro. El costo escala con el número de llamadas y el tamaño de la carga útil. En un presupuesto de CU ajustado, un bucle de registros formateados puede llevar una transacción a cómputo excedido.
Reglas prácticas:
- Prefiere una línea corta por decisión sobre volcados de múltiples líneas.
- Controla el registro detallado a través de características de Cargo en compilaciones de mainnet.
- Usa prefijos estables (
VAULT:deposit_ok) para operaciones degrep. - Prefiere eventos para la observabilidad de producción; reserva
msg!para desarrollo, Surfpool, LiteSVM e incidentes cortos. - Nunca registres secretos (semillas, metadatos privados, solo datos públicos de tablón de anuncios).
Perfila con sol_log_compute_units y establece un presupuesto de cómputo del cliente cuando las instrucciones se acerquen a los límites. Ver Registro y Costo de CU.
Decodificación del lado del cliente
Las billeteras a menudo muestran un fallo genérico. Tu dApp debería hacerlo mejor:
- Ejecuta
simulateTransaction(o confirma y obtén meta) con las mismas cuentas y datos. - Extrae el ID de programa fallido y el código personalizado (decimal o hexadecimal, por ejemplo,
0x1770= 6000). - Mapea a través de los
errorsdel IDL usandoAnchorErrorde@coral-xyz/anchor, tablas generadas por Codama, o una búsqueda fijada para @solana/kit 7.0.0. - Muestra un mensaje de producto, una cadena i18n opcional y una sugerencia de reintento específica del código.
Simular mensaje --> InstructionError + logs
|
v
program_id + code --> IDL / tabla generada
|
v
Cadena de UI + sugerencia de reintentoFija el artefacto decodificador al hash del programa desplegado. Los IDL desactualizados causan nombres de error incorrectos en los tickets de soporte. Siempre decodifica con (program_id, code) en transacciones de múltiples programas. Patrones: Decodificación de Errores del Lado del Cliente.
Mapa de observabilidad para equipos de Anchor
| Pregunta | Canal | Herramienta en cadena | Herramienta fuera de cadena |
|---|---|---|---|
| ¿Debería fallar esta ix? | Error | require! / error! / @ Variant | Decodificar código; bloquear envío en fallo de simulación |
| ¿Qué paso se ejecutó? | Registro | msg! con función activada | Explorador / sim logMessages |
| ¿Qué deberían almacenar los indexadores? | Evento | emit! / emit_cpi! | Plugin Geyser, indexador personalizado |
| ¿El estado es correcto después del éxito? | Datos de cuenta | Escrituras bajo reglas de propietario | getAccount RPC / suscripciones |
Los errores deciden el flujo de control. Los registros explican a los ingenieros. Los eventos alimentan a las máquinas. El estado de la cuenta resuelve disputas.
Consideraciones Avanzadas y Aplicaciones
Errores de restricción frente a errores del manejador. Coloca las reglas de identidad y forma de cuenta en #[derive(Accounts)] con @ YourError::Variant. Coloca las reglas de negocio que necesitan montos calculados o resultados de CPI en require! dentro del manejador.
Límites de CPI. Los errores del llamado pueden aparecer en los registros externos. Vuelve a lanzar, envuélvelos en tu propio código de UX o deja el código del llamado honesto. Decodifica con (program_id, code), no solo con el código.
Seguridad y UX. Los errores granulares ayudan a los usuarios; los excesivamente granulares pueden enseñar a los atacantes qué verificación falló. Prefiere categorías estables. Coloca detalles de caza de exploits detrás de registros no mainnet con acceso controlado, no en cadenas #[msg] permanentes.
Presupuesto de cómputo. El spam de registros o eventos puede causar fallos solo bajo carga. Perfila las CU después de las actualizaciones (este sitio fija Agave 4.1.1) y trata la eliminación de registros como un cambio de rendimiento real.
Versionado y pruebas. Vincula el binario del programa, el IDL y la tabla de errores. Archiva artefactos decodificadores por despliegue. Afirma códigos específicos en pruebas de Anchor/LiteSVM para que el renumerado silencioso falle la CI.
| Enfoque | Fortaleza | Debilidad | Mejor ajuste |
|---|---|---|---|
| Solo errores de restricción incorporados | Andamiaje rápido | UX de producto débil | Prototipos |
#[error_code] + IDL | UX estable, lista para i18n | Debe versionar cuidadosamente | Protocolos orientados al usuario |
Uso intensivo de msg! en todas partes | Depuración local rápida | CU, ruido, datos públicos | Compilaciones de desarrollo, incidentes cortos |
emit! / emit_cpi! | Esquema amigable para indexadores | CU + mantenimiento de esquema | Análisis, telemetría de protocolo |
| Simulación + decodificación en dApp | Mejor mensajería de usuario | Requiere fijación de IDL mantenida | Billeteras, UIs de trading, soporte |
Conceptos Erróneos Comunes
- "Devolveré una cadena de formato libre y la billetera la mostrará." Los clientes ven principalmente códigos y líneas de registro. Diseña variantes estables de
#[error_code]y decodifícalas tú mismo. - "
msg!es depuración gratuita." Cada llamada consume unidades de cómputo y puede fallar presupuestos ajustados. - "Los eventos son almacenamiento duradero." Son cargas útiles de registro estructuradas; el estado de la cuenta es la fuente de verdad, y las instrucciones fallidas no confirman el estado.
- "Puedo registrar semillas; solo mi equipo lee los registros." Los registros de transacciones son datos públicos de la cadena.
- "El error personalizado 6001 siempre significa lo mismo en Solana." Los códigos son por programa; siempre empareja con el ID del programa.
- "Reordenar las variantes de enum es una refactorización pura." Renumerará los códigos y romperá los mapas de clientes; solo añadir.
- "
emit!yemit_cpi!son intercambiables para todos los indexadores." La confiabilidad de captura y el costo de CU difieren; elige un estilo y verifica tu pipeline. - "El
#[msg]en cadena es suficiente para la internacionalización." Mapea códigos a cadenas de localización fuera de la cadena.
Preguntas Frecuentes
¿Cuál es la diferencia entre un error, un registro y un evento en Anchor?
Un error falla la instrucción con un código de #[error_code] y revierte el estado. Un registro es una línea msg! de texto libre para humanos. Un evento es una carga útil #[event] tipada para que los indexadores puedan decodificar campos a través del IDL.
¿Cómo asigna Anchor los códigos de error?
Los enums #[error_code] se exportan en el IDL con códigos numéricos que convencionalmente comienzan en 6000, más cadenas #[msg] opcionales para herramientas y clientes.
¿Cuándo debo usar require! vs constraint @ Error?
Usa restricciones de cuenta para validaciones disponibles en el momento de la construcción del contexto. Usa require! o error! para reglas de negocio en tiempo de ejecución, valores calculados y comprobaciones post-CPI.
¿Cuál es la diferencia entre error! y require!?
require!(condición, Error::V) afirma un booleano y devuelve el error cuando es falso. error!(Error::V) (o err!) devuelve ese error inmediatamente sin una condición. Ambos se convierten en la misma clase de fallo mapeado por IDL.
¿Por qué msg! cuesta unidades de cómputo?
El registro es una llamada al sistema en tiempo de ejecución. Más llamadas y cadenas más grandes consumen más del presupuesto de cómputo de la transacción.
¿Los programas de producción deben mantener llamadas msg! detalladas?
Generalmente no. Prefiere registros detallados con función activada, breves puntos de referencia permanentes solo donde el valor operativo es alto, y eventos estructurados para necesidades de indexadores.
emit! vs emit_cpi! ¿cuál debería elegir?
emit! es más barato y está bien para muchas aplicaciones y pruebas. emit_cpi! cuesta más CU pero mejora la confiabilidad de captura para indexadores dependientes de CPI. Estandariza por programa y valida en devnet.
¿Son visibles los eventos si la transacción falla?
Las instrucciones fallidas no confirman el estado del programa. Diseña la UX de fallo en torno a códigos de error y registros hasta el punto de fallo, no cuentas de eventos de error duraderos.
¿Cómo decodifican los clientes de @solana/kit los errores de Anchor?
Empareja el código personalizado de la simulación o meta confirmada contra el IDL o tablas generadas por Codama, fijadas a la versión del programa con la que habla tu aplicación. Siempre incluye el ID del programa en transacciones de múltiples instrucciones.
¿Pueden las restricciones de cuenta devolver errores personalizados?
Sí. Usa constraint = ... @ MyError::Variant para que los fallos de validación se mapeen a tu tabla IDL cuando necesites UX específica del producto.
¿Cómo deben decodificarse las transacciones de múltiples programas?
Recorre la pila de invocación de registros, identifica qué ID de programa falló, luego mapea la tabla de códigos de ese programa. Nunca asumas que un entero desnudo es tuyo.
¿Los eventos reemplazan las suscripciones de cuenta para los indexadores?
Son complementarios. Los eventos proporcionan hechos explícitos del dominio; las suscripciones de cuenta proporcionan diferencias de estado. Muchos pipelines de producción usan ambos.
¿Cómo mantengo las tablas de errores sincronizadas con los despliegues?
Publica el IDL en CI junto con el artefacto del programa, versiona con el hash de despliegue y rechaza lanzamientos de clientes que fijan la tabla incorrecta.
¿Dónde pertenece la i18n para los errores?
Fuera de la cadena. Mapea códigos estables a cadenas de localización en el cliente; no confíes en el #[msg] en cadena como el único texto para el usuario.
¿Cómo se mapea esto a Solana nativo sin Anchor?
Los canales de tiempo de ejecución son los mismos: ProgramError, llamadas al sistema de registro y datos de registro binarios opcionales. Sin Anchor, posees más de la historia de publicación de tablas y pierdes las macros de conveniencia.
Relacionados
- #[error_code] y require! - Declaración y afirmación de errores tipados
- Mensajes de Error Personalizados - Cadenas
#[msg]y copias de UX estables - Eventos con emit! - Cargas útiles
#[event]estructuradas para indexadores - emit_cpi! y Eventos Self-CPI - Captura confiable a través de CPI explícita
- Registro y Costo de CU - Presupuesto de
msg!y observabilidad de producción - Decodificación de Errores del Lado del Cliente - Mapeo de códigos IDL en clientes TypeScript
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.