Cómo escribir smart contracts para Solana sin volverse loco por el boilerplate
Si alguna vez intentaste escribir programas para Solana en Rust puro, probablemente recuerdas esa sensación cuando cinco líneas de lógica de negocio requerían escribir cincuenta líneas de validación de cuentas, deserialización manual mediante Borsh, y validación de firmas. Si obtenías el orden de las claves incorrecto en la transacción, recibías un error en tiempo de ejecución que a veces llevaba horas depurar.
En el mundo de Ethereum, los desarrolladores hace tiempo están acostumbrados a Solidity y wrappers listos para usar como Hardhat o Foundry. En Solana, el estándar análogo se convirtió en Anchor — un framework que maneja todo el trabajo rutinario de parsing de datos, verificaciones de control de acceso y generación de código cliente.
Lo que Anchor maneja por ti
Esencialmente, Anchor es un DSL (lenguaje de dominio específico) sobre Rust. No cambia el modelo de ejecución interno de BPF de Solana, pero empaqueta las llamadas de bajo nivel en macros declarativas comprensibles.
Cuando escribes un programa con Anchor, el framework resuelve cuatro tareas principales:
- Serialización y deserialización de datos de cuentas sin llamadas manuales a métodos de unpacking.
- Validación de restricciones de cuenta directamente en la firma de la estructura mediante atributos (verificación de owner, verificación de firma, inicialización de memoria).
- Ensamblaje de especificación IDL (Interface Description Language) — el equivalente al ABI de Ethereum.
- Creación de clientes TypeScript y Rust listos para usar para interactuar con el contrato directamente desde el frontend o las pruebas.
Cómo se ve el código en la práctica
Echemos un vistazo a un ejemplo clásico de contador. En el SDK puro de Solana, tendrías que parsear manualmente el array de bytes InstructionData, extraer slices de cuentas, verificar is_signer en la dirección del llamador, y validar la dirección del programa del sistema.
Así es como se ve el mismo contrato en Anchor:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, start: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}
La diferencia es inmediatamente evidente. Toda la lógica de validación se mueve a las estructuras Initialize y Increment.
El atributo #[account(init, payer = authority, space = 48)] le dice al runtime: crea una nueva cuenta, asigna 48 bytes para ella, y cobra la renta a la billetera authority.
El constructo #[account(mut, has_one = authority)] verifica automáticamente que el campo authority dentro de la estructura Counter coincida con la cuenta pasada authority, y que esta cuenta realmente haya firmado la transacción. Si no hay firma o se pasó una billetera diferente, la ejecución se abortará antes de entrar al cuerpo de la función increment.
IDL y Frontend sin dolor de cabeza
Lo más conveniente del bundle de Anchor es el archivo IDL en formato JSON. El compilador lo ensambla automáticamente al construir el proyecto.
El IDL describe todas las instrucciones, estructuras de datos y tipos de error personalizados posibles del programa. Basándose en este archivo, la librería @anchor-lang/core genera una interfaz tipada para JavaScript o TypeScript.
Ya no necesitas recordar offsets de bytes al ensamblar una transacción en el cliente. Llamar un método desde el frontend se convierte en una llamada de función regular:
await program.methods
.increment()
.accounts({
counter: counterPubkey,
authority: wallet.publicKey,
})
.rpc();
TypeScript resaltará errores si olvidas pasar una cuenta requerida o especificas el tipo de argumento incorrecto.
Fuzzing integrado para encontrar vulnerabilidades
En los smart contracts, el costo de un error es demasiado alto, por eso las pruebas juegan un papel especial. La CLI incluye integración con la herramienta Crucible para pruebas de fuzzing guiadas por cobertura.
El comando anchor fuzz init genera un test harness, y anchor fuzz run lo ejecuta con datos de entrada aleatorios, intentando encontrar casos extremos que conduzcan a panics o estados de cuenta inválidos. Esto ayuda a detectar desbordamientos no obvios o verificaciones omitidas antes de desplegar en testnet.
# Инициализация фазз-тестов
anchor fuzz init program_name
# Запуск тестов в release-сборке
anchor fuzz run program_name test_name --release
Instalación y primeros pasos
Para gestionar las versiones del toolchain, los desarrolladores crearon una utilidad especial llamada AVM (Anchor Version Manager). Esto te ahorra conflictos de versiones entre el compilador de Solana y el propio framework, cuando diferentes proyectos requieren diferentes sub-versiones.
La utilidad se instala con una sola línea:
curl -sSfL https://raw.githubusercontent.com/otter-sec/anchor/master/avm/install | sh
Después de la instalación, puedes cambiar a builds nocturnos o fijar releases específicos para tus workspaces:
avm nightly
avm nightly --disable
A quién le será útil Anchor
Si apenas estás comenzando con el desarrollo en Solana, empezar sin Anchor es prácticamente inútil. Pasarás semanas reinventando la rueda para el parsing de cuentas y validación de discriminadores.
El framework cubre las necesidades principales:
- Brinda a los desarrolladores de backend en Rust tipado estricto y protección contra patrones comunes de vulnerabilidades de validación.
- Brinda a los desarrolladores de frontend tipos TypeScript listos para usar y métodos convenientes para enviar transacciones.
El único caso donde Anchor podría parecer excesivo es escribir micro-programas con optimización extrema de tamaño binario (límite de presupuesto de compute), donde literalmente cada byte de instrucciones importa. En todos los demás escenarios, es el estándar de facto que ahorra cientos de horas de trabajo.
Proyectos relacionados