Serialización Borsh
Borsh (Binary Object Representation Serializer for Hashing) es la codificación canónica para los datos de instrucciones de Solana y la mayoría de las estructuras de cuentas. Es determinista, little-endian y con prefijo de longitud para las colecciones.
Receta
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Deposit {
pub amount: u64,
}
let ix_data = Deposit { amount: 1_000 }.try_to_vec()?;Cuándo usar esto:
- Definir enums de instrucciones y cargas útiles.
- Almacenar estado estructurado en cuentas de datos.
- Coincidir con la serialización de cuentas predeterminada de Anchor.
Ejemplo de Trabajo
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
};
#[derive(BorshSerialize, BorshDeserialize)]
pub enum VaultInstruction {
Deposit { amount: u64 },
Withdraw { amount: u64 },
}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct VaultState {
pub total: u64,
}
pub const VAULT_LEN: usize = 8;
pub fn process_deposit(
accounts: &[AccountInfo],
amount: u64,
) -> ProgramResult {
let iter = &mut accounts.iter();
let vault = next_account_info(iter)?;
let mut state = VaultState::deserialize(&mut &vault.data.borrow()[..])
.map_err(|_| ProgramError::InvalidAccountData)?;
state.total = state
.total
.checked_add(amount)
.ok_or(ProgramError::InvalidArgument)?;
state.serialize(&mut &mut vault.data.borrow_mut()[..])?;
Ok(())
}Lo que esto demuestra:
- Las variantes de enum de instrucciones se codifican como un discriminante
u8inicial por defecto. - Los datos de la cuenta se leen/escriben a través del slice de bytes de la cuenta.
- La aritmética verificada protege la actualización del estado antes de la serialización.
Inmersión Profunda
Cómo Funciona
- Los primitivos se codifican en ancho fijo little-endian.
StringyVec<T>se prefijan con una longitudu32.- Deserialize consume bytes secuencialmente; los bytes de cola se ignoran a menos que valide la longitud.
Planificación de Tamaño
| Campo | Bytes |
|---|---|
u64 | 8 |
Pubkey | 32 |
bool | 1 |
String | 4 + longitud utf8 |
Notas de Rust
// Usa try_to_vec para datos de instrucción; verifica que account.data.len() >= size.
const STATE_LEN: usize = core::mem::size_of::<u64>();Trampas
- Cuenta demasiado pequeña - Serialize falla o trunca si
data.len()es insuficiente. Solución: Asigna espacio exacto al crear la cuenta y almacena la constanteLEN. - Deriva del esquema - Agregar campos rompe cuentas antiguas sin migración. Solución: Byte de versión o discriminador más instrucción de migración.
- Reordenamiento de enum - Cambiar el orden de las variantes cambia los discriminantes. Solución: Agrega nuevas variantes; nunca reordenes.
- Cadenas UTF-8 - UTF-8 no válido falla la deserialización. Solución: Valida fuera de la cadena antes de enviar o usa arreglos de bytes fijos.
- Conflicto de borrow - Serializar mientras otro borrow está activo falla. Solución: Descarta los borrows inmutables antes de
borrow_mut.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| bytemuck / zero-copy | Estructuras fijas grandes | Cadenas o vectores variables |
Anchor Account<T> | Comprobaciones automáticas de diseño | Control crudo o CU mínima |
| Empaquetado manual de bytes | Diseños ultra ajustados | Mantenibilidad |
Preguntas Frecuentes
¿Borsh es lo mismo que usa Anchor?
Anchor 0.32.1 usa Borsh para los datos de la cuenta por defecto.
¿Little o big endian?
Little-endian para primitivos.
¿Cómo se codifican las Opciones?
Etiqueta de 1 byte (0/1) más el valor interno cuando es Some.
¿Puedo omitir los bytes de cola?
El deserializador se detiene al final de la estructura; valida la longitud total por seguridad.
¿Tamaño máximo de cuenta?
Límite de 10 MB por cuenta; aún paga alquiler y CUs.
¿Cómo calcular el tamaño?
Serializa la estructura predeterminada en el host o usa la constante space.
¿Vec en la cuenta?
Permitido pero aumenta el alquiler; prefiere arreglos de capacidad fija para juegos/libros de pedidos.
¿Pubkey en Borsh?
32 bytes crudos, igual que en el cable.
¿Diferencia con bincode?
Borsh es canónico para Solana; bincode no es estable entre lenguajes aquí.
¿Deserializar datos no confiables?
Siempre verifica el propietario, el discriminador y la longitud primero.
¿Soporte para i128?
Soportado - 16 bytes little-endian.
¿Probar el ciclo completo?
Usa try_to_vec y luego deserialize en pruebas unitarias en el host.
Relacionado
- Discriminadores de Cuenta - Etiquetado de tipo
- Estructuras Borsh - Diseño de diseño
- Zero-Copy con bytemuck - Alternativa de rendimiento
Versiones de Stack: 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.