Cuentas de Datos y Diseño
El estado del programa es un buffer de bytes en el campo data de una cuenta. Anchor 0.32.1 utiliza la serialización Borsh con un prefijo discriminador de 8 bytes. Un tamaño de diseño correcto evita fallos de inicialización y exploits de deserialización.
Receta
#[account]
#[derive(InitSpace)]
pub struct Market {
pub authority: Pubkey,
pub price: u64,
pub name: String, // max 32 en InitSpace vía #[max_len(32)]
}
// space = 8 + Market::INIT_SPACECuándo recurrir a esto:
- Calcular el espacio
initpara nuevos tipos de cuentas - Versionar esquemas de cuentas para actualizaciones
- Elegir Borsh vs zero-copy para compensaciones de CU y tamaño
- Analizar cuentas desconocidas desde RPC en clientes
Ejemplo Funcional
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct UserStats {
pub authority: Pubkey,
#[max_len(16)]
pub nickname: String,
pub wins: u32,
pub losses: u32,
}
#[derive(Accounts)]
pub struct InitStats<'info> {
#[account(
init,
payer = authority,
space = 8 + UserStats::INIT_SPACE,
seeds = [b"stats", authority.key().as_ref()],
bump,
)]
pub stats: Account<'info, UserStats>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[program]
pub mod stats {
use super::*;
pub fn init(ctx: Context<InitStats>, nickname: String) -> Result<()> {
require!(nickname.len() <= 16, StatsError::NameTooLong);
ctx.accounts.stats.nickname = nickname;
ctx.accounts.stats.wins = 0;
ctx.accounts.stats.losses = 0;
Ok(())
}
}Lo que esto demuestra:
InitSpacecalcula el tamaño de Borsh incluyendo cadenasmax_len- El discriminador de 8 bytes es antepuesto automáticamente por Anchor
- La ejecución valida la longitud de la cadena contra el espacio asignado
Análisis Profundo
Estructura del Diseño
[ 8 bytes discriminador ][ Campo Borsh 1 ][ Campo 2 ]...
Reglas de Dimensionamiento
- Tipos de tamaño fijo:
Pubkey32,u648,bool1 String/Vec: longitud de 4 bytes + bytes de contenido máximo- Enums: índice de variante de 1 byte + tamaño de la variante más grande
Versionado
- Añadir
version: u8como primer campo después del discriminador - O usar un nuevo tipo de cuenta con un nuevo discriminador
- Nunca reducir cuentas sin migración
Errores Comunes
- Omitir
max_lenenString/Vec- error de compilación o tamaño ilimitado. Solución: siempre establecer#[max_len(N)]en Anchor 0.32.1. - Errores de cálculo manual del espacio - error de un dígito en enums. Solución: preferir el derive
InitSpace. - Reordenar campos - rompe cuentas existentes. Solución: solo añadir nuevos campos con versionado.
- Deserializar cuentas no confiables - el atacante crea bytes maliciosos. Solución: validar propietario, discriminador y rangos de campos.
- Cuentas enormes - coste de alquiler y deserialización de CU. Solución: zero-copy o dividir entre cuentas.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Borsh + Anchor | Estado de programa predeterminado | Conjuntos de datos de varios MB |
Zero-copy (AccountLoader) | Estructuras grandes, ahorro de CU | Estructuras pequeñas y simples |
bytemuck POD | Diseños empaquetados de tamaño fijo | Cadenas de longitud variable |
| Compresión externa | Datos a escala NFT | Campos mutables de acceso frecuente |
Preguntas Frecuentes
¿Qué es el discriminador de 8 bytes?
Los primeros 8 bytes de SHA256("account:StructName") en Anchor - identifica el tipo de cuenta.
¿Cómo calculo el espacio manualmente?
Suma los tamaños de Borsh + 8. Para String(máx N): 4 + N bytes.
¿Puedo cambiar el orden de los campos en una actualización?
No - rompe la deserialización de cuentas existentes.
¿Qué es InitSpace?
Macro derive de Anchor 0.32.1 que calcula la constante INIT_SPACE para el atributo space.
¿Cómo decodifican los clientes los datos de la cuenta?
Usar clientes Codama/@solana/kit de la IDL, o lecturas manuales del buffer que coincidan con el diseño de Borsh.
¿Cuál es el tamaño máximo de cuenta?
10 MB por cuenta en Agave 4.1.1 - los límites prácticos son menores debido al alquiler/CU.
¿Puedo almacenar JSON en cuentas?
Desperdicio y sin límites. Usar diseños binarios tipados.
¿Cómo afectan los enums al espacio?
Borsh usa una etiqueta de variante de 1 byte + el tamaño de la carga útil de la variante más grande.
¿Qué es el diseño zero-copy?
#[account(zero_copy)] mapea la estructura directamente a los bytes de la cuenta sin asignación de deserialización.
¿Debería empaquetar campos manualmente?
Solo para optimización extrema de CU. El diseño predeterminado de Borsh está bien para la mayoría de los programas.
¿Cómo manejo campos opcionales?
Usar Option<T> en Borsh (etiqueta de 1 byte + T) o valores centinela.
¿Pueden dos structs compartir un discriminador?
No - cada tipo de cuenta de Anchor tiene un discriminador único.
Relacionado
- Alquiler y Exención de Alquiler - el espacio impulsa el alquiler
- Estado Propiedad del Programa - dónde residen los diseños
- Creación y Cierre de Cuentas -
reallocpara crecimiento
Versiones de la pila: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, y LiteSVM 0.6.x.