NUEVO: Helius adquiere Light Protocol
crear contratos inteligentes de Solana con Pinocchio
Blog/Desarrollo

Cómo crear programas de Solana con Pinocchio

Agencia líder de desarrollo en SolanaExo Technologies en XExo Technologies en LinkedIn
Cofundador, Exo TechnologiesTaylor Johnson en XTaylor Johnson en LinkedIn
12 min de lectura

Pinocchio es una biblioteca sin dependencias y altamente optimizada que sirve para crear programas nativos de Solana. Pinocchio fue creada por Anza, el equipo principal de desarrollo del cliente Agave de Solana. 

Exo Tech es una agencia líder de desarrollo en Solana y una de las primeras en adoptar Pinocchio. A través de nuestro trabajo con clientes, hemos desarrollado varios programas para producción con Pinocchio y contribuido al SDK para agregar funcionalidades que faltaban. 

Este artículo analiza en profundidad cómo crear programas con Pinocchio, incluidos sus beneficios y desventajas. Nuestro objetivo es brindar a los desarrolladores los conocimientos necesarios para determinar si Pinocchio es una buena opción para su programa. Sin embargo, es importante señalar que Pinocchio no es apto para principiantes, ya que prioriza la optimización sobre la experiencia del desarrollador.

¿Qué es la biblioteca Pinocchio?

La biblioteca Pinocchio reemplaza el crate solana-program y optimiza la ejecución de programas mediante un uso intensivo de tipos zero-copy. zero-copy significa que no es necesario copiar los datos a otra dirección de memoria al leerlos o escribirlos, lo que ahorra recursos de cómputo (o CU en Solana).

La biblioteca no tiene dependencias y es “no_std”. El crate std de Rust ofrece formas comunes de acceder a los recursos del sistema operativo, además de un entorno de ejecución. Sin embargo, como Solana Virtual Machine (SVM) es en sí misma un entorno de ejecución, esta sobrecarga no es necesaria.

¿Por qué Pinocchio ofrece mayor rendimiento que solana-program?

Todo programa de Solana necesita un punto de entrada que el entorno de ejecución invoque para ejecutar el programa. La biblioteca solana-program expone la macro entrypoint!, que deserializa la entrada del programa, configura un asignador de memoria heap y crea un controlador 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 tres macros de punto de entrada.

Para quienes migran desde solana-program, la macro entrypoint! funciona casi de la misma manera: deserializa la entrada del programa y configura el asignador y el controlador. 

Sin embargo, las otras dos macros desacoplan el punto de entrada de la configuración del asignador de memoria heap y del controlador de pánico. Esto da al desarrollador mayor control para omitirlos u optimizarlos antes de ejecutar la lógica del programa. 

program_entrypoint! deserializa la entrada del programa de forma similar a solana-program, mientras que lazy_program_entrypoint! simplemente encapsula el búfer de entrada y delega su gestión al programa, lo que ofrece mayor control sobre el cómputo. 

Como estas macros no configuran el asignador de memoria heap ni el controlador de pánico, la biblioteca Pinocchio expone macros predeterminadas que el desarrollador puede usar.

Por otra parte, si un programa sabe que nunca necesitará memoria heap, no_allocator! ahorra unidades de cómputo (CU) al omitir la configuración de un asignador de memoria.

¿Cómo deserializan los puntos de entrada de Pinocchio las entradas de los programas de Solana de forma diferente?

Mencionamos brevemente cómo los puntos de entrada de solana-program y Pinocchio deserializan la entrada del programa. Aun así, es importante entender sus diferencias, ya que de ahí provienen los principales ahorros de CU. 

A primera vista, las entradas deserializadas que se pasan al controlador de instrucciones del programa parecen iguales:

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

La diferencia clave está en la implementación de AccountInfo. 

Mientras que solana-program escribe los datos en una estructura AccountInfo que es propietaria de ellos, la estructura AccountInfo de Pinocchio es simplemente un puntero a los datos de entrada subyacentes que representan la cuenta. Esto reduce la cantidad de datos que deben copiarse y ahorra muchas CU.

¿Cómo permite Pinocchio que los desarrolladores optimicen las CU?

Como el procesador de instrucciones recibe referencias a punteros, quienes usan la biblioteca Pinocchio notarán que su lógica casi nunca tiene la propiedad de los datos con los que trabaja. 

Esto puede verse fácilmente al intentar acceder a los valores de AccountInfo. Leer la clave pública de la cuenta con el método key() devuelve una referencia a Pubkey. Esto reduce el costo de leer la información de la cuenta durante la ejecución del programa y de modificar sus datos.

Ejemplo de optimización de CU con Pinocchio: P-token

Un excelente ejemplo que sigue usando zero-copy para optimizar es el programa p-token.

Este programa está diseñado para reemplazar el programa canónico SPL Token, pero usa Pinocchio para reducir drásticamente la cantidad de unidades de cómputo de cada transacción.

Notarás rápidamente que se accede a todo el estado mediante punteros.

En lugar de deserializar la cuenta del token, se verifican los datos de AccountInfo y luego se devuelve un puntero.

Se accede a cada propiedad mediante una función, y todos los valores que no son primitivos devuelven una referencia que mantiene zero-copy. 

Para saber más sobre por qué esto reduce drásticamente el uso de CU, consulta este artículo sobre la optimización de CU.

Pinocchio frente a Anchor

Anchor es un framework con convenciones definidas muy popular para desarrollar programas de Solana. Se considera de mayor nivel que Pinocchio porque no contiene lógica para exponer estructuras subyacentes como AccountInfo.

En cambio, Anchor depende del crate solana-program antes mencionado y expone traits y macros para agilizar el desarrollo de programas. Anchor ofrece patrones de discriminadores de instrucciones y lógica de deserialización de cuentas. Esta lógica depende de Borsh, que requiere copiar los datos a otra dirección de memoria porque no usa zero-copy. 

Aunque la comodidad de Anchor acelera el desarrollo de programas de Solana, también aumenta el uso de CU.

Pinocchio, en cambio, es una biblioteca diseñada para reemplazar solana-program cuando los desarrolladores necesitan ajustar con precisión el uso de cómputo. No impone ninguna convención y permite estructurar el programa como el desarrollador considere adecuado. El diseño de cada proyecto de Pinocchio puede ser totalmente distinto, mientras que los proyectos de Anchor tienen estructuras claramente definidas. 

La biblioteca Pinocchio no gestiona bindings ni implementaciones del cliente. Anchor, en cambio, ofrece soporte de primera clase para generar IDL, que pueden usarse del lado del cliente para interactuar con el programa.

Quienes usan Pinocchio deben escribirlos por su cuenta o usar otras herramientas como Shank y Codama, que describimos más adelante en la sección Herramientas complementarias para desarrollar con Pinocchio.

Pinocchio frente a Steel

Steel es otro framework para escribir programas de Solana. Actualmente construido sobre solana-program, Steel expone macros, funciones y patrones que facilitan la creación de programas seguros y expresivos.

Las convenciones definidas de Steel facilitan la lectura del código sin perder modularidad. Los desarrolladores pueden optar por usar solo los componentes de Steel que necesitan, a diferencia de Anchor, que es un framework de todo o nada.

La macro account! de Steel usa bytemuck para analizar las estructuras de las cuentas, mientras que Pinocchio no gestiona su análisis. Esto también incluye analizadores y aserciones encadenables, lo que facilita agregar validaciones personalizadas. Pinocchio no incluye estos patrones de forma predeterminada, por lo que el desarrollador debe escribir los suyos.

Sin embargo, para las invocaciones comunes entre programas (CPI), como las del System Program y el Token Program, tanto Pinocchio como Steel exponen patrones que facilitan estas invocaciones.

Pinocchio está altamente optimizado, pero deja cada detalle en manos del desarrollador. Steel es una capa modular sobre la biblioteca solana-program diseñada para mejorar la experiencia del desarrollador.

Cómo crear un token con Pinocchio

Para mostrar un programa escrito con Pinocchio, reescribiremos el programa para crear tokens de los ejemplos para desarrolladores de Solana.

Es un programa sencillo con una sola instrucción que crea un mint de token Token2022 y usa la extensión de token Metadata para almacenar información sobre el token. Los metadatos se proporcionan mediante datos de instrucción que contienen un nombre, un símbolo y un uri.

1. Define un punto de entrada

Comencemos por definir el punto de entrada de nuestro programa.

Usaremos la macro completa del punto de entrada porque queremos utilizar el asignador predeterminado y la gestión de pánico de Pinocchio.

Código
entrypoint!(process_instruction);

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

2. Define la estructura de los datos de instrucción

A continuación, definimos la estructura de nuestros datos de instrucción para que coincida con la de los otros programas de ejemplo. Para ahorrar tiempo de desarrollo, usaremos Borsh para la deserialización y dejaremos los métodos de deserialización más óptimos para otro artículo.

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

3. Analiza las cuentas y los datos de instrucción

Ahora escribiremos la lógica de nuestro procesador de instrucciones.

Lo primero que debemos hacer es desestructurar las cuentas de la lista de cuentas y deserializar los datos de instrucción en nuestro 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. Crea la cuenta de mint de Token2022

Una vez analizadas las cuentas y los datos de instrucción, invocamos la instrucción CreateAccount del System Program.

A continuación, usamos la estructura CreateAccount de `pinocchio_system crate as it makes it very convenient to CPI by setting values of the struct and calling invoke.

A diferencia de la creación de un mint SPL Token normal, debemos determinar el espacio adicional que requieren las extensiones de token que usamos.

El tamaño de la extensión Metadata Pointer es estático, mientras que el de la extensión Token Metadata debe calcularse de forma dinámica según los argumentos proporcionados.

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()?;

Después de invocar CreateAccount, SystemProgram registra el programa Token2022 como propietario de la cuenta de mint.

5. Inicializa la extensión, la cuenta y los valores de metadatos

A continuación, debemos establecer los datos de la cuenta. Para ello, inicializamos la extensión Metadata Pointer, inicializamos la cuenta de mint con el programa Token2022 e inicializamos los valores de metadatos que nuestro programa recibió como argumentos. 

Las siguientes CPI provienen de una rama del crate pinocchio_token que está en desarrollo activo. Por eso, conviene señalar que es probable que este código quede desactualizado, ya que se planea separar la funcionalidad de Token2022 del 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()?;

¡Eso es todo! 

Ahora tenemos un mint de token con metadatos autocontenidos que usa Token2022 y está escrito con Pinocchio.

Este código puede mejorarse para garantizar la máxima optimización, pero esperamos que te ayude a entender cómo escribir programas con Pinocchio.

Herramientas complementarias para desarrollar con Pinocchio

Las herramientas específicas para Pinocchio son escasas, pero siguen creciendo.

Bytemuck para (des)serializar cuentas

El desarrollador de un programa de Pinocchio debe implementar la (des)serialización de cuentas. Hacerlo manualmente es un proceso tedioso y propenso a errores. Bytemuck es una excelente biblioteca que facilita la lectura y escritura de arreglos de bytes como estructuras. Está bastante optimizada porque limita la cantidad de datos que deben copiarse en la memoria.

Borsh es otra solución para trabajar con cuentas que no tienen tamaños fijos, aunque consume más recursos de cómputo. Esta es una de las razones por las que algunos prefieren Pinocchio sobre Anchor.

Shank para generar IDL

Como Pinocchio es una biblioteca, no incluye generación de IDL como Anchor. Una IDL (lenguaje de definición de interfaces) es un archivo JSON que define la interfaz pública de un programa de Solana, incluidas sus instrucciones, estructuras de cuentas y códigos de error. Esto permite interacciones estandarizadas y simplifica el desarrollo del lado del cliente.

Para generar IDL, recomendamos usar Shank. Este crate permite que los desarrolladores anoten su código y usen una CLI para generar fácilmente una IDL válida. Agregar la macro ShankAccount a la declaración derive de una estructura indica que se trata de una cuenta que debe poder (des)serializarse. Al ejecutar la CLI de shank, esta estructura aparecerá como una cuenta con tipos en la IDL y podrá usarse para generar clientes.

Otra macro importante es ShankInstruction para el enum de instrucciones del programa. Esta permite usar el atributo #[account] para indicar el índice y los permisos de cada cuenta de la lista para esa instrucción específica.

Consulta el repositorio shank-macro para obtener más información sobre las útiles anotaciones de código que facilitan la generación de IDL para programas que no usan Anchor.

Codama para generar clientes

Una vez que tienes una IDL, generar clientes es fácil con Codama. Si el código generado no satisface tus necesidades, tendrás que escribir los clientes manualmente.

En Exo Tech, creamos una plantilla de proyecto de Pinocchio para configurar rápidamente repositorios de programas de Solana. Pruébala y abre un pull request si tienes alguna mejora.

El futuro de Pinocchio

Aunque se diseñó como un reemplazo directo de solana-program, Pinocchio aún no ofrece todas sus funcionalidades. Algunas sysvars todavía no son compatibles y ciertos crates secundarios no tienen soporte completo o no existen. Por ejemplo, el crate del programa Token de Pinocchio no admite múltiples firmantes. Tampoco admite Token2022, aunque esta funcionalidad está en desarrollo.

Una de las desventajas más importantes de Pinocchio es que todos los SDK desarrollados para otros programas de Solana usan el crate solana-program. Esto significa que cada SDK necesita ser propietario de AccountInfo o de los datos que se transfieren, lo que dificulta enormemente la interoperabilidad con un programa desarrollado con Pinocchio. 

Al integrar programas de terceros, es muy común tener que escribir lógica de CPI personalizada para cada instrucción. Es posible que esto se resuelva con generadores de código como Codama, pero aún no hemos llegado a ese punto.

Es importante señalar que Pinocchio sigue en desarrollo activo y no ha sido auditado. La comunidad continúa trabajando para incluir el resto de las sysvars en el SDK y mejorar el soporte de programas SPL importantes como Token y Token2022.

Cómo contribuir a Pinocchio

Hay muchas contribuciones sencillas que puedes hacer a Pinocchio.

Hay issues abiertos y pull requests existentes que necesitan más apoyo. Únete a las conversaciones o simplemente abre un pull request para que los mantenedores lo revisen.

Conclusión

Pinocchio ofrece un rendimiento drásticamente superior al de las soluciones anteriores para escribir programas de Solana. Al dar a los desarrolladores más flexibilidad sobre el punto de entrada de su programa y usar zero-copy para acceder a las entradas, puede ayudarles a reducir el uso de CU. Sin embargo, sigue siendo una biblioteca nueva y aún no ofrece todas las funcionalidades. Al momento de escribir este artículo, la biblioteca no ha sido auditada, así que úsala con precaución.

Al evaluar si conviene usar Pinocchio, es importante comparar sus ventajas y desventajas con las de otras bibliotecas y frameworks.

Los frameworks con convenciones definidas, como Anchor, aceleran el desarrollo de programas y facilitan su mantenimiento. Esto los convierte en una excelente opción cuando es importante lanzar el producto rápidamente.

Cuando tu producto sea estable y procese un gran volumen de transacciones, puede ser más adecuado optimizar los programas de Solana con una biblioteca como Pinocchio.

Recursos adicionales

Para obtener más información, mira la presentación de Febo en Solana Accelerate 2025 y explora estos recursos educativos:

Suscríbete a Helius

Mantente al día con las novedades del desarrollo en Solana y recibe actualizaciones cuando publiquemos

Imagen ampliada