NOVO: Helius adquire a Light Protocol
crie contratos inteligentes da Solana com Pinocchio
Blog/Desenvolvimento

Como criar programas Solana com Pinocchio

Agência líder em desenvolvimento SolanaExo Technologies no XExo Technologies no LinkedIn
Cofundador da Exo TechnologiesTaylor Johnson no XTaylor Johnson no LinkedIn
12 min de leitura

Pinocchio é uma biblioteca altamente otimizada e sem dependências que pode ser usada para criar programas Solana nativos. Pinocchio foi criado pela Anza, responsável pelo desenvolvimento principal do cliente Agave da Solana. 

A Exo Tech é uma das principais empresas de desenvolvimento Solana e foi uma das primeiras a adotar Pinocchio. Por meio do nosso trabalho com clientes, desenvolvemos vários programas em produção usando Pinocchio e contribuímos com o SDK para adicionar funcionalidades que faltavam. 

Este artigo apresenta uma análise detalhada da criação de programas com Pinocchio, incluindo seus benefícios e limitações. Nosso objetivo é oferecer aos desenvolvedores o conhecimento necessário para determinar se Pinocchio é adequado para seus programas. No entanto, é importante observar que Pinocchio não é indicado para iniciantes, pois prioriza a otimização em vez da experiência do desenvolvedor.

O que é a biblioteca Pinocchio?

A biblioteca Pinocchio substitui o crate solana-program e otimiza a execução de programas por meio do uso extensivo de tipos zero-copy. zero-copy significa que os dados não precisam ser copiados para outro endereço de memória durante a leitura ou a gravação, o que economiza recursos computacionais, ou CUs na Solana.

A biblioteca não tem dependências e é “no_std”. O crate std do Rust oferece formas comuns de acessar recursos do sistema operacional, além de um ambiente de execução. Porém, como a Solana Virtual Machine (SVM) já é um ambiente de execução, essa sobrecarga é desnecessária.

Por que Pinocchio oferece melhor desempenho que solana-program?

Todo programa Solana precisa de um ponto de entrada que o ambiente de execução chama para executar o programa. A biblioteca solana-program expõe a macro entrypoint!, que desserializa a entrada do programa, configura um alocador de heap e cria um manipulador de pânico.

Código
macro_rules! entrypoint {
    ($process_instruction:ident) => {
        /// # Safety
        #[no_mangle]
        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
            let (program_id, accounts, instruction_data) =
                unsafe { $crate::entrypoint::deserialize(input) };
            match $process_instruction(&program_id, &accounts, &instruction_data) {
                Ok(()) => $crate::entrypoint::SUCCESS,
                Err(error) => error.into(),
            }
        }
        $crate::custom_heap_default!();
        $crate::custom_panic_default!();
    };
}

Pinocchio exporta três macros de ponto de entrada.

Para quem está migrando de solana-program, a macro entrypoint! funciona praticamente da mesma forma: desserializa a entrada do programa e configura o alocador e o manipulador. 

No entanto, as outras duas macros separam o ponto de entrada da configuração do alocador de heap e do manipulador de pânico. Assim, o desenvolvedor tem mais controle para omitir ou otimizar essas etapas antes da execução da lógica do programa. 

program_entrypoint! desserializa a entrada do programa de forma semelhante a solana-program, enquanto lazy_program_entrypoint! apenas encapsula o buffer de entrada e delega o processamento ao programa, oferecendo mais controle sobre a computação. 

Como essas macros não configuram o alocador de heap nem o manipulador de pânico, a biblioteca Pinocchio expõe macros padrão que o desenvolvedor pode usar.

Além disso, se um programa souber que nunca precisará de memória heap, no_allocator! economizará unidades computacionais (CUs) ao dispensar a configuração de um alocador de memória.

Como os pontos de entrada do Pinocchio desserializam as entradas de programas Solana de maneira diferente?

Mencionamos brevemente como os pontos de entrada de solana-program e Pinocchio desserializam a entrada do programa. Ainda assim, é importante entender como essas desserializações diferem, pois é daí que vem grande parte da economia de CUs. 

À primeira vista, as entradas desserializadas passadas ao manipulador de instruções do programa parecem iguais:

Código
/// solana-program and pinocchio both look the same
process_instruction(
         program_id: &Pubkey,
         accounts: &[AccountInfo],
         instruction_data: &[u8],
     ) -> ProgramResult

A principal diferença está na implementação de AccountInfo. 

Enquanto solana-program grava os dados em uma struct AccountInfo que detém esses dados, a struct AccountInfo do Pinocchio é apenas um ponteiro para os dados de entrada subjacentes que representam a conta. Isso reduz a quantidade de dados que precisa ser copiada e economiza muitas CUs.

Como Pinocchio permite que os desenvolvedores otimizem CUs?

Como o processador de instruções recebe referências a ponteiros, os desenvolvedores que usam a biblioteca Pinocchio perceberão que sua lógica quase nunca detém a propriedade dos dados com os quais trabalha. 

Isso fica evidente ao tentar acessar valores em AccountInfo. A leitura da chave pública da conta com o método key() retorna uma referência a Pubkey. Isso reduz o custo de leitura das informações da conta durante a execução do programa e também o custo de alteração dos dados da conta.

Exemplo de otimização de CUs com Pinocchio: P-token

Um excelente exemplo que continua usando o acesso sem cópia para otimizações é o programa p-token.

Esse programa foi desenvolvido para substituir o SPL Token Program canônico, mas usa Pinocchio para reduzir drasticamente a quantidade de unidades computacionais de cada transação.

Você perceberá rapidamente que todo o estado é acessado por meio de ponteiros.

Em vez de desserializar a conta de token, os dados de AccountInfo são verificados e, depois, um ponteiro é retornado.

Cada propriedade é acessada por meio de uma função, e todos os valores que não são primitivos retornam uma referência, mantendo o acesso sem cópia. 

Para entender melhor por que isso reduz drasticamente o uso de CUs, confira este artigo sobre otimização de CUs.

Pinocchio vs. Anchor

Anchor é um framework opinativo muito popular para desenvolver programas Solana. Ele é considerado de nível mais alto que Pinocchio porque não contém lógica para expor estruturas subjacentes como AccountInfo.

Em vez disso, Anchor depende do crate solana-program mencionado anteriormente e expõe traits e macros que simplificam o processo de desenvolvimento de programas. Anchor fornece padrões de discriminadores de instruções e lógica de desserialização de contas. A lógica de desserialização depende de Borsh, que exige a cópia dos dados para outro endereço de memória porque não oferece acesso sem cópia. 

Embora a conveniência do Anchor acelere o processo de desenvolvimento de programas Solana, ela aumenta o uso de CUs.

Pinocchio, por outro lado, é uma biblioteca criada para substituir solana-program quando os desenvolvedores precisam ajustar com precisão o uso de recursos computacionais. Ela não impõe uma abordagem específica e permite que o desenvolvedor estruture o programa da forma que considerar adequada. Cada projeto Pinocchio pode ter uma organização completamente diferente, enquanto os projetos Anchor têm estruturas claramente definidas. 

A biblioteca Pinocchio não gerencia bindings nem implementações para clientes. Anchor, por outro lado, oferece suporte nativo à geração de IDLs, que podem ser usadas no lado do cliente para interagir com o programa.

Os desenvolvedores que usam Pinocchio precisam criar suas próprias soluções ou usar outras ferramentas, como Shank e Codama, descritas abaixo na seção Ferramentas complementares para desenvolver com Pinocchio.

Pinocchio vs. Steel

Steel é outro framework para criar programas Solana. Atualmente desenvolvido sobre solana-program, Steel expõe macros, funções e padrões que facilitam a criação de programas seguros e expressivos.

A natureza opinativa do Steel facilita a leitura e preserva a modularidade. Os desenvolvedores podem usar somente os componentes do Steel de que precisam, ao contrário do Anchor, que é um framework do tipo tudo ou nada.

A macro account! do Steel usa bytemuck para analisar as estruturas das contas, enquanto Pinocchio não processa a análise de contas. Isso também inclui analisadores encadeáveis e asserções, facilitando a inclusão de validações personalizadas. Pinocchio não oferece esses padrões por padrão, então o desenvolvedor precisa criar seus próprios padrões de validação.

No entanto, para invocações comuns entre programas (CPIs), como System Program e Token Program, tanto Pinocchio quanto Steel expõem padrões que facilitam essas invocações.

Pinocchio é altamente otimizado, mas deixa todos os detalhes sob responsabilidade do desenvolvedor. Steel é uma boa camada modular sobre a biblioteca solana-program, criada para melhorar a experiência do desenvolvedor.

Como criar um token usando Pinocchio

Para demonstrar um programa escrito com Pinocchio, vamos reescrever o programa de criação de tokens dos exemplos para desenvolvedores da Solana.

Este é um programa simples com uma única instrução que cria uma emissão de token Token2022 e usa a extensão de token Metadata para armazenar informações sobre o token. Os metadados serão fornecidos por meio dos dados da instrução, que contêm nome, símbolo e uri.

1. Defina um ponto de entrada

Vamos começar definindo o ponto de entrada do nosso programa.

Usaremos a macro completa de ponto de entrada porque queremos usar o alocador padrão e o tratamento de pânico do Pinocchio.

Código
entrypoint!(process_instruction);

fn process_instruction(
   _program_id: &Pubkey,
   accounts: &[AccountInfo],
   instruction_data: &[u8],
) -> ProgramResult {
   Ok(())
}

2. Defina a estrutura dos dados da instrução

Em seguida, definimos a estrutura dos dados da nossa instrução para que corresponda à dos outros programas de exemplo. Para economizar tempo de desenvolvimento, usaremos Borsh para a desserialização e deixaremos métodos de desserialização mais eficientes para outro artigo.

Código
#[derive(BorshDeserialize, Debug)]
pub struct CreateTokenArgs {
   pub name: String,
   pub symbol: String,
   pub uri: String,
   pub decimals: u8,
}

3. Analise as contas e os dados da instrução

Agora vamos escrever a lógica no processador de instruções.

Primeiro, precisamos desestruturar as contas da lista de contas e desserializar os dados da instrução em nosso CreateTokenArgs.

Código
let [mint_account, mint_authority, payer, token_program, _system_program] = accounts else {
       return Err(ProgramError::NotEnoughAccountKeys);
   };

   let args = CreateTokenArgs::try_from_slice(instruction_data)
       .map_err(|_| ProgramError::InvalidInstructionData)?;

4. Crie a conta de emissão Token2022

Com as contas e os dados da instrução analisados, invocamos a instrução CreateAccount do System Program.

Abaixo, usamos a struct CreateAccount de `pinocchio_system crate as it makes it very convenient to CPI by setting values of the struct and calling invoke.

Diferentemente da criação de uma emissão SPL Token normal, precisamos determinar o espaço adicional exigido pelas extensões de token que estamos usando.

O tamanho da extensão Metadata Pointer é estático, enquanto o da extensão Token Metadata precisa ser calculado dinamicamente com base nos argumentos fornecidos.

Código
 /// [4 (extension discriminator) + 32 (update_authority) + 32 (metadata)]
   const METADATA_POINTER_SIZE: usize = 4 + 32 + 32;
   /// [4 (extension discriminator) + 32 (update_authority) + 32 (mint) + 4 (size of name ) + 4 (size of symbol) + 4 (size of uri) + 4 (size of additional_metadata)]
   const METADATA_EXTENSION_BASE_SIZE: usize = 4 + 32 + 32 + 4 + 4 + 4 + 4;
   /// Padding used so that Mint and Account extensions start at the same index
   const EXTENSIONS_PADDING_AND_OFFSET: usize = 84;

   /* within `process_instruction` */
   let extension_size = METADATA_POINTER_SIZE
       + METADATA_EXTENSION_BASE_SIZE
       + args.name.len()
       + args.symbol.len()
       + args.uri.len();
   let total_mint_size = Mint::LEN + EXTENSIONS_PADDING_AND_OFFSET + extension_size;

   let rent = Rent::get()?;
   // Create the account for the Mint
   CreateAccount {
       from: payer,
       to: mint_account,
       owner: token2022_program.key(),
       lamports: rent.minimum_balance(Mint::LEN),
       space: Mint::LEN as u64,
   }
   .invoke()?;

Depois que CreateAccount é invocado, SystemProgram registra o programa Token2022 como proprietário da conta de emissão.

5. Inicialize a extensão, a conta e os valores dos metadados

Em seguida, precisamos definir os dados da conta inicializando a extensão Metadata Pointer, inicializando a conta de emissão com o programa Token2022 e inicializando os valores de metadados que nosso programa recebeu como argumentos. 

As CPIs a seguir vêm de uma branch do crate pinocchio_token que está em desenvolvimento ativo. Portanto, vale observar que esse código provavelmente ficará desatualizado, pois há planos de separar a funcionalidade Token2022 do crate SPL Token.

Código
// Initialize MetadataPointer extension pointing to the Mint account
   InitializeMetadataPointer {
       mint: mint_account,
       authority: Some(*payer.key()),
       metadata_address: Some(*mint_account.key()),
   }
   .invoke()?;

   // Now initialize that account as a Token2022 Mint
   InitializeMint2 {
       mint: mint_account,
       decimals: args.decimals,
       mint_authority: mint_authority.key(),
       freeze_authority: None,
   }
   .invoke(TokenProgramVariant::Token2022)?;

   // Set the metadata within the Mint account
   InitializeTokenMetadata {
       metadata: mint_account,
       update_authority: payer,
       mint: mint_account,
       mint_authority: payer,
       name: &args.name,
       symbol: &args.symbol,
       uri: &args.uri,
   }
   .invoke()?;

É isso! 

Agora temos uma emissão de token com metadados autocontidos usando Token2022, criada com Pinocchio.

Esse código ainda pode ser aprimorado para garantir o máximo de otimização, mas esperamos que ele ajude você a entender como criar programas com Pinocchio.

Ferramentas complementares para desenvolver com Pinocchio

As ferramentas específicas para Pinocchio ainda são limitadas, mas estão evoluindo.

Bytemuck para (des)serializar contas

A (des)serialização de contas precisa ser implementada pelo desenvolvedor de um programa Pinocchio. Quando feito manualmente, esse processo é trabalhoso e sujeito a erros. Bytemuck é uma excelente biblioteca que facilita a leitura e a gravação de arrays de bytes como structs. Isso oferece uma boa otimização ao limitar a quantidade de dados que precisa ser copiada para a memória.

Borsh é outra solução para trabalhar com contas que não têm tamanhos fixos, embora seja menos eficiente em termos computacionais e um dos motivos pelos quais as pessoas preferem Pinocchio a Anchor.

Shank para gerar IDLs

Como Pinocchio é uma biblioteca, ele não oferece geração integrada de IDLs como Anchor. Uma IDL (Interface Definition Language) é um arquivo JSON que define a interface pública de um programa Solana, incluindo suas instruções, estruturas de contas e códigos de erro. Isso permite interações padronizadas e simplifica o desenvolvimento no lado do cliente.

Para gerar IDLs, recomendamos usar Shank. Esse crate facilita muito a anotação de código pelos desenvolvedores e o uso de uma CLI para gerar uma IDL válida. Adicionar a macro ShankAccount à declaração derive de uma struct indica que ela é uma conta que deve ser (des)serializável. Depois que a CLI do Shank é executada, essa estrutura aparece como uma conta tipada na IDL e pode ser usada para gerar clientes.

Outra macro importante é ShankInstruction para o enum de instruções do programa. Ela permite usar um atributo #[account] para indicar o índice e as permissões de cada conta na lista daquela instrução específica.

Consulte o repositório shank-macro para saber mais sobre as anotações de código úteis que facilitam a geração de IDLs para programas que não usam Anchor.

Codama para gerar clientes

Depois que você tem uma IDL, fica fácil gerar clientes com Codama. Se o código gerado não atender às suas necessidades, você precisará criar os clientes manualmente.

Na Exo Tech, criamos um template de projeto Pinocchio para iniciar rapidamente repositórios de programas Solana. Fique à vontade para testá-lo e abrir um pull request com qualquer melhoria!

O futuro do Pinocchio

Embora tenha sido criado para substituir diretamente solana-program, Pinocchio ainda não oferece os mesmos recursos. Algumas sysvars ainda não são compatíveis, e crates que não fazem parte do núcleo ainda não têm suporte completo ou não existem. Por exemplo, vários signatários não são compatíveis com o crate do programa Token do Pinocchio. Também não há suporte para Token2022, embora ele esteja em desenvolvimento.

Uma das desvantagens mais significativas de usar Pinocchio é que todos os SDKs desenvolvidos para outros programas Solana usam o crate solana-program. Isso significa que cada SDK precisa deter a propriedade de AccountInfo ou dos dados transmitidos, o que dificulta muito a interoperabilidade com um programa desenvolvido usando Pinocchio. 

Ao integrar programas de terceiros, é muito comum precisar criar uma lógica de CPI personalizada para cada instrução. Isso pode acabar sendo resolvido por geradores de código como Codama, mas eles ainda não chegaram lá.

É importante observar que Pinocchio ainda está em desenvolvimento ativo e não foi auditado. A comunidade continua trabalhando para incluir as demais sysvars no SDK e melhorar o suporte a programas SPL importantes, como Token e Token2022.

Como contribuir com Pinocchio

Há muitas contribuições simples que podem ser feitas para Pinocchio.

Há issues abertas e pull requests existentes que precisam de apoio adicional. Participe das conversas ou simplesmente abra um pull request para que os mantenedores possam analisá-lo!

Conclusão

Pinocchio é uma biblioteca com desempenho muito superior ao das soluções anteriores para criar programas Solana. Oferecer aos desenvolvedores mais flexibilidade sobre o ponto de entrada do programa e usar zero-copy para acessar as entradas do programa pode ajudar a reduzir o uso de CUs. No entanto, ela ainda é uma biblioteca nova e não tem todos os recursos. Até o momento desta publicação, a biblioteca não foi auditada. Portanto, use-a com cautela.

Ao avaliar se você deve usar Pinocchio, é importante considerar as vantagens e limitações em relação a outras bibliotecas e frameworks.

Frameworks opinativos como Anchor aceleram o desenvolvimento de programas e facilitam a manutenção, sendo uma excelente opção quando o tempo de lançamento é importante.

Quando seu produto estiver estável e processando um grande volume de transações, otimizar programas Solana com uma biblioteca como Pinocchio pode ser mais adequado.

Recursos adicionais

Para saber mais, assista à apresentação de Febo na Solana Accelerate 2025 e explore estes recursos educacionais:

Assine a Helius

Acompanhe as novidades mais recentes do desenvolvimento Solana e receba atualizações quando publicarmos

Imagem ampliada