Serialização Borsh
Borsh (Binary Object Representation Serializer for Hashing) é a codificação canônica para dados de instrução Solana e a maioria das structs de conta. É determinístico, little-endian e com prefixo de comprimento para coleções.
Receita
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Deposit {
pub amount: u64,
}
let ix_data = Deposit { amount: 1_000 }.try_to_vec()?;Quando usar:
- Definir enums e payloads de instrução.
- Armazenar estado estruturado em contas de dados.
- Corresponder à serialização padrão de conta do Anchor.
Exemplo Funcional
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(())
}O que isso demonstra:
- Variantes de enum de instrução codificam com discriminante
u8inicial por padrão. - Dados de conta são lidos/escritos pelo slice de bytes da conta.
- Aritmética verificada protege a atualização de estado antes da serialização.
Aprofundamento
Como Funciona
- Primitivos codificam em largura fixa little-endian.
StringeVec<T>prefixam com comprimentou32.- Deserialize consome bytes sequencialmente - bytes trailing são ignorados a menos que você valide comprimento.
Planejamento de Tamanho
| Campo | Bytes |
|---|---|
u64 | 8 |
Pubkey | 32 |
bool | 1 |
String | 4 + utf8 len |
Notas Rust
// Use try_to_vec para dados de instrução; verifique account.data.len() >= size.
const STATE_LEN: usize = core::mem::size_of::<u64>();Armadilhas
- Conta pequena demais - Serialize faz panic ou trunca se
data.len()for insuficiente. Correção: Aloque espaço exato na criação da conta e armazene constanteLEN. - Deriva de schema - Adicionar campos quebra contas antigas sem migração. Correção: Byte de versão ou discriminador mais instrução de migração.
- Reordenar enum - Mudar ordem de variantes muda discriminantes. Correção: Acrescente novas variantes; nunca reordene.
- Strings UTF-8 - UTF-8 inválido falha deserialização. Correção: Valide off-chain antes de enviar ou use arrays de bytes fixos.
- Conflito de borrow - Serializar com outro borrow ativo faz panic. Correção: Solte borrows imutáveis antes de
borrow_mut.
Alternativas
| Alternativa | Use quando | Não use quando |
|---|---|---|
| bytemuck / zero-copy | Structs fixas grandes | Strings ou vectors variáveis |
Anchor Account<T> | Checagens automáticas de layout | Controle raw ou CU mínimo |
| Empacotamento manual de bytes | Layouts ultra-apertados | Manutenibilidade |
FAQs
Borsh é o mesmo que Anchor usa?
Anchor 0.32.1 usa Borsh para dados de conta por padrão.
Little ou big endian?
Little-endian para primitivos.
Como Options são codificados?
Tag de 1 byte (0/1) mais valor interno quando Some.
Posso pular bytes trailing?
Deserializer para no fim da struct; valide comprimento total por segurança.
Tamanho máximo de conta?
Cap de 10 MB por conta - ainda paga aluguel e CUs.
Como calcular tamanho?
Serialize struct default no host ou use constante space.
Vec em conta?
Permitido mas aumenta aluguel; prefira arrays de capacidade fixa para games/order books.
Pubkey em Borsh?
32 bytes raw - igual no wire.
Diferença de bincode?
Borsh é canônico para Solana; bincode não é estável cross-language aqui.
Deserializar dados não confiáveis?
Sempre verifique owner, discriminador e comprimento primeiro.
Suporte i128?
Suportado - 16 bytes little-endian.
Testar roundtrip?
Use try_to_vec depois deserialize em testes unitários no host.
Relacionado
- Account Discriminators - Tagging de tipo
- Borsh Structs - Design de layout
- Zero-Copy with bytemuck - Alternativa de performance
Versões da stack: Esta página foi 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 e LiteSVM 0.6.x.