borsh & bytemuck
Borsh (Binary Object Representation Serializer for Hashing) é o formato de serialização padrão para dados de conta e instrução do Anchor no Agave 4.1.1. bytemuck permite casting zero-copy de layouts de dados simples (plain-old-data) quando você controla o alinhamento e a validação. Escolha Borsh para flexibilidade; escolha bytemuck para caminhos críticos sensíveis a CU.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
[dependencies]
borsh = "1.5"
bytemuck = { version = "1.16", features = ["derive"] }use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Config {
pub bump: u8,
pub fee_bps: u16,
}Quando usar isso:
- Codificar argumentos de instrução e estado da conta em programas nativos ou Anchor.
- Analisar dados de conta em clientes e testes Rust.
- Otimizar contas grandes de layout fixo com leituras zero-copy.
- Compartilhar esquemas com indexadores off-chain (discriminador + cauda Borsh).
Exemplo de Trabalho
use borsh::{BorshDeserialize, BorshSerialize};
use bytemuck::{Pod, Zeroable};
#[derive(BorshSerialize, BorshDeserialize, Clone)]
pub struct UserMeta {
pub authority: [u8; 32],
pub points: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct LedgerHeader {
pub magic: u32,
pub count: u32,
}
pub fn parse_ledger(data: &[u8]) -> Result<(&LedgerHeader, &[u8]), &'static str> {
if data.len() < std::mem::size_of::<LedgerHeader>() {
return Err("too short");
}
let (head_bytes, rest) = data.split_at(std::mem::size_of::<LedgerHeader>());
let header: &LedgerHeader = bytemuck::from_bytes(head_bytes);
if header.magic != 0xC0FFEE {
return Err("bad magic");
}
Ok((header, rest))
}O que isso demonstra:
- Borsh deriva structs serializáveis para instruções.
Pod+Zeroablemarcam layouts transparentes seguros para bytemuck.- Validação manual (
magic, comprimento) precede o cast zero-copy. - Bytes de cauda podem conter entradas codificadas em Borsh após um cabeçalho fixo.
Mergulho Profundo
Como Funciona
- Borsh escreve um layout canônico little-endian com fatias pré-fixadas por comprimento.
- Anchor anexa discriminadores de 8 bytes aos payloads de conta e instrução.
- bytemuck reinterpreta fatias de bytes como
&Tsem cópia quando as regras de alinhamento são atendidas. - Clientes (Rust, TS, Python) devem espelhar o mesmo esquema para decodificação.
Tabela de Crates Rust
| Crate | Papel | On-chain |
|---|---|---|
borsh | Serializar/desserializar structs | Sim |
borsh-derive | Macros derive | Sim |
bytemuck | Casts zero-copy | Sim (com cuidado) |
bytemuck_derive | Derives Pod/Zeroable | Sim |
anchor-lang | Borsh integrado para contas | Sim (caminho Anchor) |
Tabela TypeScript / NPM
| Pacote | Papel | Notas |
|---|---|---|
borsh (npm) | Decodificação off-chain em JS | Corresponder exatamente ao layout Rust |
Codecs @solana/kit | Codecs binários para mensagens de tx | 7.0.0 |
| Codecs gerados por Codama | Layouts baseados em IDL | Preferido para clientes de aplicativos |
Notas Rust
// Conta Anchor com discriminador
// space = 8 + UserMeta::serialized_size() // ou INIT_SPACE em Anchor mais recente
// Nunca use bytemuck-cast em contas não confiáveis sem verificações de proprietário + comprimentoArmadilhas
- bytemuck em structs packed ou com muitos bools - risco de comportamento indefinido. Correção:
repr(C)+ campos de preenchimento explícitos; teste no alvo. - Mudança de esquema sem migração - desserializa lixo. Correção: byte de versão + instrução de migração.
- Misturar Serde JSON com dados on-chain - formato diferente. Correção: Apenas Borsh (ou customizado) dentro de programas.
try_from_slicenão verificado em dados do usuário - causa pânico no código nativo. Correção:try_from_slice->ProgramError::InvalidAccountData.- Suposições de Endianness - Borsh é LE; bytemuck segue o host para POD - use apenas tipos LE.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
bincode | Ferramentas apenas off-chain | On-chain (não padrão) |
Traço Pack manual (estilo SPL legado) | Interoperação com layouts antigos | Novas contas Anchor |
serde JSON | APIs e configurações | Armazenamento de conta |
Estilo memmap apenas off-chain | Análise | Tempo de execução do programa |
FAQs
O Anchor sempre usa Borsh?
Sim, para corpos de conta/instrução, a menos que você opte por serialização personalizada.
Quando zero-copy vale a pena?
Quando a profilagem mostra que a desserialização domina a CU em registros grandes e fixos.
bytemuck com Account<T> do Anchor?
Possível para layouts híbridos; muitas equipes usam AccountInfo bruto para caudas zero-copy.
Vetor em Borsh on-chain?
Permitido, mas custoso - prefira arrays de capacidade fixa em contas críticas.
Decodificação Python?
borsh-construct ou parsers de IDL do AnchorPy - mantenha os esquemas sincronizados.
Colisões de Discriminador?
Anchor hashea nomes de conta/instrução - não reutilize nomes entre tipos descuidadamente.
Endianness em clientes TS?
Use Codecs Codama/@solana/kit para evitar erros manuais.
Fuzzing de layouts?
Teste de decodificação Borsh com bytes arbitrários em testes LiteSVM 0.6.x.
Redimensionamento de conta?
Dados variáveis Borsh precisam de instruções realloc - planeje o espaço com antecedência.
Hashing com Borsh?
Borsh é determinístico - útil para design de pré-imagem de semente PDA (com separação de domínio).
Relacionado
- Serialização Borsh - guia aprofundado de Borsh
- Zero-Copy com bytemuck - padrões de otimização
- Serialização de Dados de Conta - visão geral da seção
- anchor-lang & anchor-spl - derives integrados
Versões da Pilha: 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.