Rust
Pinocchio para Iniciantes

Pinocchio para Iniciantes

Leitura e Escrita de Dados

Ao construir programas Solana otimizados, a serialização e desserialização eficiente de dados pode impactar significativamente o desempenho.

Embora o Pinocchio não exija operações de memória de baixo nível, entender como ler e escrever dados de conta de forma eficiente pode ajudá-lo a construir programas mais rápidos.

As técnicas neste guia funcionam com qualquer framework de desenvolvimento Solana; seja usando Pinocchio, Anchor ou o SDK nativo. A chave é projetar suas estruturas de dados de forma inteligente e lidar com a serialização com segurança.

Quando Usar Código Unsafe

Use código unsafe apenas quando:

  • Você precisa de máximo desempenho e mediu que alternativas seguras são muito lentas

  • Você pode verificar rigorosamente todas as invariantes de segurança

  • Você documenta os requisitos de segurança claramente

Prefira alternativas seguras quando possível.

Princípios de Segurança

Ao trabalhar com arrays de bytes brutos e operações de memória, devemos ter cuidado para evitar comportamento indefinido. Entender esses princípios é crucial para escrever código correto e confiável.

Verificação de Limites do Buffer

Sempre valide que seu buffer é grande o suficiente antes de qualquer operação de leitura ou escrita. Ler ou escreber além da memória alocada é comportamento indefinido.

rust
// Bom: Verifica limites primeiro
if data.len() < size_of::<u64>() {
    return Err(ProgramError::InvalidInstructionData);
}
let value = u64::from_le_bytes(data[0..8].try_into().unwrap());

// Ruim: Sem verificação de limites - pode causar panic ou UB
let value = u64::from_le_bytes(data[0..8].try_into().unwrap());

Requisitos de Alinhamento

Cada tipo em Rust possui um requisito de alinhamento que determina onde pode ser colocado na memória. Ler um tipo de memória que não está propriamente alinhado resulta em comportamento indefinido. A maioria dos tipos primitivos requer alinhamento igual ao seu tamanho:

  • u8: alinhamento de 1 byte

  • u16: alinhamento de 2 bytes

  • u32: alinhamento de 4 bytes

  • u64: alinhamento de 8 bytes

Isso significa que um u64 deve ser armazenado em endereços de memória divisíveis por 8, enquanto um u16 deve começar em endereços pares.

Se isso não for respeitado, o compilador inserirá automaticamente bytes de "padding" invisíveis entre os campos da struct para garantir que cada campo atenda aos seus requisitos de alinhamento.

Além disso, o tamanho total da struct deve ser múltiplo do requisito de alinhamento do seu maior campo.

Assim é como fica uma struct com ordenação ruim:

rust
#[repr(C)]
struct BadOrder {
    small: u8,    // 1 byte
    // padding: [u8; 7] já que `big` precisa ser alinhado em 8 bytes.
    big: u64,     // 8 bytes  
    medium: u16,  // 2 bytes
    // padding: [u8; 6] já que o tamanho da struct precisa ser alinhado a 8 bytes.
}

O compilador insere 7 bytes de padding após small porque big requer alinhamento de 8 bytes. Depois adiciona mais 6 bytes no final para que o tamanho total (24 bytes) seja múltiplo de 8, desperdiçando 13 bytes.

Uma forma melhor seria ordenar os campos da struct assim:

rust
#[repr(C)]
struct GoodOrder {
    big: u64,     // 8 bytes
    medium: u16,  // 2 bytes  
    small: u8,    // 1 byte
    // padding: [u8; 5] já que o tamanho da struct precisa ser alinhado a 8 bytes.
}

Ao colocar campos maiores primeiro, reduzimos o padding de 13 bytes para apenas 5 bytes.

A regra de ouro é ordenar os campos da struct do maior para o menor requisito de alinhamento para minimizar o padding e reduzir o uso de memória.

Existe outra forma, mais avançada, de serializar e desserializar dados para máxima eficiência de espaço. Podemos criar Structs Zero-Padding onde os requisitos de alinhamento são eliminados inteiramente:

rust
#[repr(C)]
struct ByteArrayStruct {
    big: [u8; 8],    // representa u64
    medium: [u8; 2], // representa u16  
    small: u8,
}

O tamanho neste caso é exatamente 11 bytes porque tudo tem alinhamento de 1 byte.

Padrões de Bits Válidos

Nem todos os padrões de bits são válidos para cada tipo. Tipos como bool, char e enums possuem valores válidos restritos. Ler padrões de bits inválidos para esses tipos é comportamento indefinido.

Lendo Dados

Existem várias abordagens para ler dados de buffers de conta, cada uma com diferentes trade-offs:

Desserialização Campo a Campo (Recomendado)

A abordagem mais segura é desserializar cada campo individualmente. Isso evita todos os problemas de alinhamento porque você está trabalhando com arrays de bytes:

rust
pub struct DepositInstructionData {
    pub amount: u64,
    pub recipient: Pubkey,
}

impl<'a> TryFrom<&'a [u8]> for DepositInstructionData {
    type Error = ProgramError;

    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
        if data.len() < (size_of::<u64>() + size_of::<Pubkey>()) {
            return Err(ProgramError::InvalidInstructionData);
        }

        // Sem problemas de alinhamento: estamos lendo bytes e convertendo
        let amount = u64::from_le_bytes(
            data[0..8].try_into()
                .map_err(|_| ProgramError::InvalidInstructionData)?
        );
        
        let recipient = Pubkey::try_from(&data[8..40])
            .map_err(|_| ProgramError::InvalidInstructionData)?;

        Ok(Self { amount, recipient })
    }
}

Desserialização Zero-Copy

Isso pode ser usado para máximo desempenho com structs devidamente alinhadas, mas requer verificação cuidadosa de alinhamento:

rust
#[repr(C)]
pub struct Config {
    pub authority: Pubkey,
    pub mint_x: Pubkey, 
    pub mint_y: Pubkey,
    pub seed: u64,        // Este campo requer alinhamento de 8 bytes
    pub fee: u16,         // Este campo requer alinhamento de 2 bytes  
    pub state: u8,
    pub config_bump: u8,
}

impl Config {
    pub const LEN: usize = size_of::<Self>();
    
    pub fn from_bytes(data: &[u8]) -> Result<&Self, ProgramError> {
        if data.len() != Self::LEN {
            return Err(ProgramError::InvalidAccountData);
        }

        // Crítico: Verifica alinhamento para o campo mais restritivo (u64 neste caso)
        if (data.as_ptr() as usize) % core::mem::align_of::<Self>() != 0 {
            return Err(ProgramError::InvalidAccountData);
        }

        // SAFETY: Verificamos comprimento e alinhamento
        Ok(unsafe { &*(data.as_ptr() as *const Self) })
    }
}

// Alternativa: Evita problemas de alinhamento inteiramente usando arrays de bytes
// para tipos com requisito de alinhamento maior que 1 e fornecendo métodos acessadores
#[repr(C)]
pub struct ConfigSafe {
    pub authority: Pubkey,
    pub mint_x: Pubkey, 
    pub mint_y: Pubkey,
    seed: [u8; 8],      // Converter com u64::from_le_bytes quando necessário
    fee: [u8; 2],       // Converter com u16::from_le_bytes quando necessário
    pub state: u8,
    pub config_bump: u8,
}

impl ConfigSafe {
    pub fn from_bytes(data: &[u8]) -> Result<&Self, ProgramError> {
        if data.len() != size_of::<Self>() {
            return Err(ProgramError::InvalidAccountData);
        }

        // SAFETY: Sem necessidade de verificação de alinhamento - tudo está alinhado a u8
        Ok(unsafe { &*(data.as_ptr() as *const Self) })
    }
    
    pub fn seed(&self) -> u64 {
        u64::from_le_bytes(self.seed)
    }
    
    pub fn fee(&self) -> u16 {
        u16::from_le_bytes(self.fee)
    }
}

Como você pode ver, ambos os campos seed e fee são privados. Isso porque devemos sempre usar métodos acessadores para ler dados, já que seus valores são representados por arrays de bytes.

Quando você acessa um campo diretamente (config.seed), o compilador pode precisar criar uma referência ao local de memória daquele campo, mesmo temporariamente. Se esse campo não estiver propriamente alinhado, criar a referência é comportamento indefinido, mesmo que você nunca use explicitamente a referência!

Métodos acessadores evitam isso realizando a operação de leitura dentro do escopo do método, onde o compilador pode otimizar qualquer referência intermediária.

rust
#[repr(C, packed)]  // Isso pode causar campos desalinhados!
pub struct PackedConfig {
    pub state: u8,
    pub seed: u64,    // Este u64 pode não estar alinhado a 8 bytes devido ao packing
}

impl PackedConfig {
    pub fn seed(&self) -> u64 {
        self.seed  // Seguro: Cópia direta do valor, sem referência criada
    }
}

// Uso:
let config = PackedConfig::load(account)?;

// ❌ COMPORTAMENTO INDEFINIDO: Cria referência a campo potencialmente desalinhado
let seed_ref = &config.seed; // Compilador precisa criar uma referência aqui!

// ❌ COMPORTAMENTO INDEFINIDO: Até isso pode ser problemático
let seed_value = config.seed; // Pode criar referência temporária internamente

// ✅ SEGURO: Método acessador lê valor sem criar referência
let seed_value = config.seed(); // Sem referência intermediária

Campos u8 são sempre seguros de acessar diretamente, já que u8 tem alinhamento de 1 e está sempre alinhado, então podemos usar self.state diretamente.

Neste caso não temos nenhum "tipo especial", mas sempre lembre que alguns tipos exigem cuidado extra devido a padrões de bits inválidos:

rust
pub struct StateAccount {
    pub is_active: bool,
    pub state_type: StateType,
    pub data: [u8; 32],
}

#[repr(u8)]
pub enum StateType {
    Inactive = 0,
    Active = 1,
    Paused = 2,
}

impl StateAccount {
    pub fn from_bytes(data: &[u8]) -> Result<Self, ProgramError> {
        if data.len() < size_of::<Self>() {
            return Err(ProgramError::InvalidAccountData);
        }

        // Trata bool de forma segura (apenas 0 ou 1 são válidos)
        let is_active = match data[0] {
            0 => false,
            1 => true,
            _ => return Err(ProgramError::InvalidAccountData),
        };

        // Trata enum de forma segura
        let state_type = match data[1] {
            0 => StateType::Inactive,
            1 => StateType::Active, 
            2 => StateType::Paused,
            _ => return Err(ProgramError::InvalidAccountData),
        };

        let mut data_array = [0u8; 32];
        data_array.copy_from_slice(&data[2..34]);

        Ok(Self {
            is_active,
            state_type,
            data: data_array,
        })
    }
}

Padrões Perigosos a Evitar

Aqui estão padrões comuns que podem levar a comportamento indefinido e devem ser evitados:

  1. Usar transmute() com Dados Desalinhados

rust
// ❌ COMPORTAMENTO INDEFINIDO: transmute requer alinhamento adequado
let value: u64 = unsafe { core::mem::transmute(bytes_slice) };

transmute() presume que os dados de origem estão propriamente alinhados para o tipo destino. Se você está trabalhando com slices de bytes arbitrários, essa suposição é frequentemente violada.

  1. Cast de Ponteiro para Packed Structs

rust
#[repr(C, packed)]
pub struct PackedConfig {
    pub state: u8,
    pub seed: u64,     // Este u64 está alinhado a apenas 1 byte!
    pub authority: Pubkey,
}

// ❌ COMPORTAMENTO INDEFINIDO: Cria referências a campos desalinhados
let config = unsafe { &*(data.as_ptr() as *const PackedConfig) };
let seed_value = config.seed; // UB: Pode criar referência a u64 desalinhado

Embora a struct caiba na memória, acessar campos multi-byte pode criar referências desalinhadas.

  1. Acesso Direto a Campos em Packed Structs

rust
#[repr(C, packed)]
pub struct PackedStruct {
    pub a: u8,
    pub b: u64,
}

let packed = /* ... */;
// ❌ COMPORTAMENTO INDEFINIDO: Cria referência a campo desalinhado
let b_ref = &packed.b;
// ❌ COMPORTAMENTO INDEFINIDO: Pode criar referência temporária
let b_value = packed.b;
  1. Assumir Alinhamento Sem Verificação

rust
// ❌ COMPORTAMENTO INDEFINIDO: Sem verificação de alinhamento
let config = unsafe { &*(data.as_ptr() as *const Config) };

Só porque os dados cabem não significa que estão alinhados corretamente.

  1. Usar read_unaligned() Incorretamente

rust
// ❌ ERRADO: read_unaligned precisa de layout adequado, não apenas tamanho
#[repr(Rust)]  // Layout padrão - não garantido!
pub struct BadStruct {
    pub field: u64,
}

let value = unsafe { (data.as_ptr() as *const BadStruct).read_unaligned() };

read_unaligned() ainda requer que a struct tenha um layout previsível (#[repr(C)]).

Escrevendo Dados

Escrever dados de forma segura segue princípios similares à leitura:

Serialização Campo a Campo (Recomendado)

rust
impl Config {
    pub fn write_to_buffer(&self, data: &mut [u8]) -> Result<(), ProgramError> {
        if data.len() != Self::LEN {
            return Err(ProgramError::InvalidAccountData);
        }

        let mut offset = 0;
        
        // Escreve authority
        data[offset..offset + 32].copy_from_slice(self.authority.as_ref());
        offset += 32;
        
        // Escreve mint_x  
        data[offset..offset + 32].copy_from_slice(self.mint_x.as_ref());
        offset += 32;
        
        // Escreve mint_y
        data[offset..offset + 32].copy_from_slice(self.mint_y.as_ref());
        offset += 32;
        
        // Escreve seed
        data[offset..offset + 8].copy_from_slice(&self.seed.to_le_bytes());
        offset += 8;
        
        // Escreve fee
        data[offset..offset + 2].copy_from_slice(&self.fee.to_le_bytes());
        offset += 2;
        
        // Escreve state
        data[offset] = self.state;
        offset += 1;
        
        // Escreve config_bump
        data[offset] = self.config_bump;

        Ok(())
    }
}

Esta abordagem é o método mais seguro porque serializa explicitamente cada campo no buffer de bytes:

  • Sem preocupações com alinhamento: você está escrevendo em um array de bytes

  • Endianness explícita: você controla a ordem dos bytes com to_le_bytes()

  • Layout de memória claro: fácil de depurar e entender

  • Sem comportamento indefinido: todas as operações são em arrays de bytes

Mutação Direta (Zero-Copy)

Para máximo desempenho, você pode fazer cast do buffer de bytes para uma struct e mutar campos diretamente. Isso requer que a struct esteja propriamente alinhada:

rust
impl Config {
    pub fn from_bytes_mut(data: &mut [u8]) -> Result<&mut Self, ProgramError> {
        if data.len() != Self::LEN {
            return Err(ProgramError::InvalidAccountData);
        }

        // Verifica alinhamento
        if (data.as_ptr() as usize) % core::mem::align_of::<Self>() != 0 {
            return Err(ProgramError::InvalidAccountData);
        }

        // SAFETY: Verificamos comprimento e alinhamento
        Ok(unsafe { &mut *(data.as_mut_ptr() as *mut Self) })
    }
}

Quando o alinhamento é verificado e a struct usa #[repr(C)], mutação direta de campos não cria referências desalinhadas.

Abordagem com Array de Bytes e Setters (Mais Segura + Rápida)

O melhor dos dois mundos: podemos usar arrays de bytes internamente mas fornecer setters ergonômicos:

rust
#[repr(C)]
pub struct ConfigSafe {
    pub authority: Pubkey,
    pub mint_x: Pubkey, 
    pub mint_y: Pubkey,
    seed: [u8; 8],
    fee: [u8; 2],
    pub state: u8,
    pub config_bump: u8,
}

impl ConfigSafe {
    pub fn from_bytes_mut(data: &mut [u8]) -> Result<&mut Self, ProgramError> {
        if data.len() != size_of::<Self>() {
            return Err(ProgramError::InvalidAccountData);
        }

        // Sem necessidade de verificação de alinhamento - tudo está alinhado a u8
        Ok(unsafe { &mut *(data.as_mut_ptr() as *mut Self) })
    }
    
    pub fn seed(&self) -> u64 {
        u64::from_le_bytes(self.seed)
    }
    
    pub fn fee(&self) -> u16 {
        u16::from_le_bytes(self.fee)
    }

    // Setters que tratam endianness corretamente

    pub fn set_seed(&mut self, seed: u64) {
        self.seed = seed.to_le_bytes();
    }
    
    pub fn set_fee(&mut self, fee: u16) {
        self.fee = fee.to_le_bytes();
    }
}

Isso é ideal porque:

  • Sem problemas de alinhamento: todos os campos são alinhados a bytes

  • Mutação direta rápida: sem overhead de serialização após configuração inicial

  • Endianness consistente: setters tratam conversão de ordem de bytes

  • Segurança de tipos: setters recebem os tipos esperados, não arrays de bytes

Dados de Tamanho Dinâmico

Sempre que possível, evite armazenar dados de tamanho dinâmico diretamente em contas. No entanto, alguns casos de uso exigem isso.

Se sua conta contém dados dinâmicos, sempre coloque todos os campos de tamanho estático no início da sua struct e adicione os dados dinâmicos no final.

Campo Dinâmico Único

Este é o caso mais simples: uma seção de comprimento variável no final da sua conta:

rust
#[repr(C)]
pub struct DynamicAccount {
    pub fixed_data: [u8; 32],
    pub counter: u64,
    // Dados dinâmicos seguem após a struct na memória
    // Layout: [fixed_data][counter][dynamic_data...]
}

impl DynamicAccount {
    pub const FIXED_SIZE: usize = size_of::<Self>();
    
    /// Analisa conta com dados dinâmicos de forma segura
    pub fn from_bytes_with_dynamic(data: &[u8]) -> Result<(&Self, &[u8]), ProgramError> {
        if data.len() < Self::FIXED_SIZE {
            return Err(ProgramError::InvalidAccountData);
        }

        // SAFETY: Verificamos que o buffer é grande o suficiente para a parte fixa
        // A parte fixa contém apenas [u8; 32] e u64, que têm layout previsível
        let fixed_part = unsafe { &*(data.as_ptr() as *const Self) };
        
        // Tudo após a parte fixa são dados dinâmicos
        let dynamic_part = &data[Self::FIXED_SIZE..];
        
        Ok((fixed_part, dynamic_part))
    }
    
    /// Obtém referências mutáveis para ambas as partes
    pub fn from_bytes_mut_with_dynamic(data: &mut [u8]) -> Result<(&mut Self, &mut [u8]), ProgramError> {
        if data.len() < Self::FIXED_SIZE {
            return Err(ProgramError::InvalidAccountData);
        }

        // Divide o buffer para evitar problemas de borrowing
        let (fixed_bytes, dynamic_bytes) = data.split_at_mut(Self::FIXED_SIZE);
        
        // SAFETY: Verificamos o tamanho e dividimos de forma segura
        let fixed_part = unsafe { &mut *(fixed_bytes.as_mut_ptr() as *mut Self) };
        
        Ok((fixed_part, dynamic_bytes))
    }
}

/// Escrita de campo dinâmico único
impl DynamicAccount {
    pub fn write_with_dynamic(
        data: &mut [u8], 
        fixed_data: &[u8; 32], 
        counter: u64,
        dynamic_data: &[u8]
    ) -> Result<(), ProgramError> {
        let total_size = Self::FIXED_SIZE + dynamic_data.len();
        
        if data.len() != total_size {
            return Err(ProgramError::InvalidAccountData);
        }
        
        // Escreve parte fixa campo a campo (abordagem mais segura)
        data[0..32].copy_from_slice(fixed_data);
        data[32..40].copy_from_slice(&counter.to_le_bytes());
        
        // Escreve parte dinâmica
        data[Self::FIXED_SIZE..].copy_from_slice(dynamic_data);
        
        Ok(())
    }
    
    /// Atualiza apenas a porção dinâmica
    pub fn update_dynamic_data(&mut self, account_data: &mut [u8], new_data: &[u8]) -> Result<(), ProgramError> {
        if account_data.len() < Self::FIXED_SIZE + new_data.len() {
            return Err(ProgramError::InvalidAccountData);
        }
        
        // Escreve novos dados dinâmicos
        account_data[Self::FIXED_SIZE..Self::FIXED_SIZE + new_data.len()].copy_from_slice(new_data);
        
        Ok(())
    }
}

Para evitar comportamento indefinido, sempre verifique que o buffer de dados da conta é pelo menos tão grande quanto a porção de tamanho estático. A seção dinâmica pode estar vazia, então essa verificação é essencial.

Este layout garante que os offsets para campos de tamanho fixo são sempre conhecidos, independentemente do comprimento dos dados dinâmicos.

Existem dois cenários principais ao ler dados de tamanho dinâmico:

  • Campo dinâmico único no final: Você pode facilmente determinar o tamanho e offset dos dados dinâmicos em tempo de execução assim:

rust
const DYNAMIC_DATA_START_OFFSET: usize = size_of::<[u8; 32]>();

#[repr(C)]
pub struct DynamicallySizedAccount {
    pub sized_data: [u8; 32],
    // pub dynamic_data: &'info [u8], // Não faz parte da struct, mas segue no buffer
}

impl DynamicallySizedAccount {
    /// Retorna o comprimento da seção de dados dinâmicos.
    #[inline(always)]
    pub fn get_dynamic_data_len(data: &[u8]) -> Result<usize, ProgramError> {
        if data.len().le(&DYNAMIC_DATA_START_OFFSET) {
            return Err(ProgramError::InvalidAccountData);
        }

        Ok(data.len() - DYNAMIC_DATA_START_OFFSET)
    }

    /// Retorna um slice dos dados dinâmicos.
    #[inline(always)]
    pub fn read_dynamic_data(data: &[u8]) -> Result<&[u8], ProgramError> {
        if data.len().le(&DYNAMIC_DATA_START_OFFSET) {
            return Err(ProgramError::InvalidAccountData);
        }

        Ok(&data[DYNAMIC_DATA_START_OFFSET..])
    }
}

Múltiplos Campos Dinâmicos

Esta abordagem é mais complexa, pois precisaremos de uma forma de determinar o comprimento de cada campo dinâmico, exceto o último. A abordagem mais comum é prefixar cada campo dinâmico (exceto o último) com seu comprimento, para que possamos analisar o buffer corretamente.

Aqui está um padrão simples e robusto: armazene o comprimento do primeiro campo dinâmico como um u8 (ou u16, etc. se precisar de tamanhos maiores) imediatamente após os dados de tamanho estático. O primeiro campo dinâmico segue, e o segundo campo dinâmico ocupa o restante do buffer.

rust
#[repr(C)]  
pub struct MultiDynamicAccount {
    pub fixed_data: [u8; 32],
    pub timestamp: u64,
    // Layout: [fixed_data][timestamp][len1: u8][data1][data2: remainder]
}

impl MultiDynamicAccount {
    pub const FIXED_SIZE: usize = size_of::<Self>();
    pub const LEN_PREFIX_SIZE: usize = size_of::<u8>();
    pub const MIN_SIZE: usize = Self::FIXED_SIZE + Self::LEN_PREFIX_SIZE;
    
    /// Analisa conta com duas seções dinâmicas
    pub fn parse_dynamic_fields(data: &[u8]) -> Result<(&[u8; 32], u64, &[u8], &[u8]), ProgramError> {
        if data.len() < Self::MIN_SIZE {
            return Err(ProgramError::InvalidAccountData);
        }
        
        // Extrai dados fixos com segurança
        let fixed_data = data[..32].try_into()
            .map_err(|_| ProgramError::InvalidAccountData)?;
            
        let timestamp = u64::from_le_bytes(
            data[32..40].try_into()
                .map_err(|_| ProgramError::InvalidAccountData)?
        );
            
        // Lê comprimento do primeiro campo dinâmico (único byte)
        let len = data[Self::FIXED_SIZE] as usize;
        
        // Valida que temos dados suficientes
        if data.len() <  Self::MIN_SIZE + len {
            return Err(ProgramError::InvalidAccountData);
        }
        
        let data_1 = &data[Self::MIN_SIZE..Self::MIN_SIZE + len];
        let data_2 = &data[Self::MIN_SIZE + len..]; // Restante
        
        Ok((fixed_data, timestamp, data_1, data_2))
    }
    
    /// Escreve conta com duas seções dinâmicas
    pub fn write_with_multiple_dynamic(
        buffer: &mut [u8],
        fixed_data: &[u8; 32],
        timestamp: u64,
        data_1: &[u8],
        data_2: &[u8]
    ) -> Result<(), ProgramError> {
        let total_size = Self::MIN_SIZE + data_1.len() + data_2.len();
        
        if buffer.len() != total_size {
            return Err(ProgramError::InvalidAccountData);
        }
        
        // Valida que o comprimento de data_1 cabe em u8
        if data_1.len() > u8::MAX as usize {
            return Err(ProgramError::InvalidInstructionData);
        }
        
        let mut offset = 0;
        
        // Escreve dados fixos
        buffer[offset..offset + 32].copy_from_slice(fixed_data);
        offset += 32;
        
        buffer[offset..offset + 8].copy_from_slice(&timestamp.to_le_bytes());
        offset += 8;
        
        // Escreve prefixo de comprimento para data1 (único byte)
        buffer[offset] = data_1.len() as u8;
        offset += 1;
        
        // Escreve data1
        buffer[offset..offset + data_1.len()].copy_from_slice(data_1);
        offset += data_1.len();
        
        // Escreve data2 (restante - sem prefixo de comprimento necessário)
        buffer[offset..].copy_from_slice(data_2);
        
        Ok(())
    }
}

Redimensionando a conta

Toda vez que você atualizar um campo dinâmico, se o tamanho mudar, você deve redimensionar a conta. Aqui está uma função de propósito geral para redimensionar uma conta:

rust
pub fn resize_account(
    account: &AccountInfo,
    payer: &AccountInfo,
    new_size: usize,
    zero_out: bool,
) -> ProgramResult {
    // Se a conta já tem o tamanho correto, retorna cedo
    if new_size == account.data_len() {
        return Ok(());
    }

    // Calcula requisitos de rent
    let rent = Rent::get()?;
    let new_minimum_balance = rent.minimum_balance(new_size);

    // Ajusta lamports para atender requisitos de isenção de rent
    match new_minimum_balance.cmp(&account.lamports()) {
        core::cmp::Ordering::Greater => {
            // Precisa de mais lamports para isenção de rent
            let lamports_diff = new_minimum_balance.saturating_sub(account.lamports());
            **payer.try_borrow_mut_lamports()? -= lamports_diff;
            **account.try_borrow_mut_lamports()? += lamports_diff;
        }
        core::cmp::Ordering::Less => {
            // Retorna lamports excedentes ao pagador
            let lamports_diff = account.lamports().saturating_sub(new_minimum_balance);
            **account.try_borrow_mut_lamports()? -= lamports_diff;
            **payer.try_borrow_mut_lamports()? += lamports_diff;
        }
        core::cmp::Ordering::Equal => {
            // Sem necessidade de transferência de lamports
        }
    }

    // Realoca a conta
    account.resize(new_size)?;

    Ok(())
}
Blueshift © 2026Commit: 1b88646