El IDL (Interface Definition Language) es un archivo JSON que describe cómo interactuar con un programa de Solana. Es generado automáticamente por el framework Anchor.
No hay nada especial en la función llamada “initialize” — es un nombre que Anchor elige. Lo que aprenderemos en este tutorial es cómo las pruebas unitarias en typescript son capaces de “encontrar” la función adecuada.
Creemos un nuevo proyecto llamado anchor-function-tutorial y cambiemos el nombre en la función initialize a boaty_mc_boatface, manteniendo todo lo demás igual.
pub fn boaty_mc_boatface(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
Ahora cambiemos la prueba a lo siguiente:
it("Call boaty mcboatface", async () => {
// Add your test here.
const tx = await program.methods.boatyMcBoatface().rpc();
console.log("Your transaction signature", tx);
});
Ahora ejecuta las pruebas con anchor test --skip-local-validator
Se ejecuta como se esperaba. Entonces, ¿cómo funcionó esta hechicería?
¿Cómo saben las pruebas de la existencia de la función initialize?
Cuando Anchor compila un programa de Solana, crea un IDL (Interface Definition Language).
Esto se almacena en target/idl/anchor_function_tutorial.json. Este archivo se llama anchor_function_tutorial.json porque anchor_function_tutorial es el nombre del programa. ¡Nota que Anchor convirtió los guiones en guiones bajos!
Vamos a abrirlo.
{
"version": "0.1.0",
"name": "anchor_function_tutorial",
"instructions": [
{
"name": "boatyMcBoatface",
"accounts": [],
"args": []
}
]
}
La lista de “instructions” (instrucciones) representa las funciones expuestas públicamente que soporta el programa, más o menos equivalente a las funciones externas y públicas en un contrato de Ethereum. Un archivo IDL en Solana juega un papel similar al del archivo ABI en Solidity, especificando cómo interactuar con el programa/contrato.
Vimos anteriormente que nuestra función no tomaba ningún argumento, por eso la lista
argsestá vacía. Más adelante explicaremos qué es “accounts”.
Una cosa que destaca: las funciones en Rust usan snake_case, pero Anchor las formatea en el entorno de JavaScript como camelCase. Esto es para respetar las convenciones de los lenguajes: Rust tiende a usar snake case y JavaScript típicamente usa camel case.
Este archivo JSON es la forma en que el objeto “methods” sabe qué funciones soportar.
Cuando ejecutamos la prueba, esperamos que pase, lo que significa que la prueba está llamando correctamente al programa de Solana:

Ejercicio: Añade un argumento a la función boaty_mc_boatface para que reciba un u64. Ejecuta anchor build nuevamente. Luego, vuelve a abrir el archivo target/idl/anchor_function_tutorial.json. ¿Cómo cambia?
Ahora vamos a empezar a crear un programa de Solana que tenga funciones para suma y resta básicas que impriman el resultado. Las funciones en Solana no pueden retornar valores de la misma manera que Solidity, así que tendremos que imprimirlos. (Solana tiene formas alternativas de pasar valores que discutiremos más adelante). Creemos dos funciones de la siguiente manera:
pub fn add(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
let sum = a + b;
msg!("Sum is {}", sum);
Ok(())
}
pub fn sub(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
let difference = a - b;
msg!("Difference is {}", difference);
Ok(())
}
Y cambiemos nuestras pruebas unitarias a lo siguiente:
it("Should add", async () => {
const tx = await program.methods.add(new anchor.BN(1), new anchor.BN(2)).rpc();
console.log("Your transaction signature", tx);
});
it("Should sub", async () => {
const tx = await program.methods.sub( new anchor.BN(10), new anchor.BN(3)).rpc();
console.log("Your transaction signature", tx);
});
Ejercicio: Implementa funciones similares para mul, div y modulo, y escribe una prueba unitaria para invocar cada una.
¿Qué pasa con el struct Initialize?
Ahora hay otra cosa curiosa ocurriendo aquí. Hemos dejado el struct Initialize intacto y lo estamos reutilizando entre funciones. Una vez más, el nombre no importa. Cambiemos el nombre del struct a Empty y volvamos a ejecutar la prueba.
//...
// Change struct name here
pub fn add(ctx: Context<Empty>, a: u64, b: u64) -> Result<()> {
let sum = a + b;
msg!("Sum is {}", sum);
Ok(())
}
//...
// Change struct name here too
#[derive(Accounts)]
pub struct Empty {}
De nuevo, el nombre Empty es totalmente arbitrario aquí.
Ejercicio: Cambia el nombre del struct Empty a BoatyMcBoatface y vuelve a ejecutar las pruebas.
¿Qué es el struct #[derive(Accounts)]?
Esta sintaxis con # es un atributo de Rust definido por el framework Anchor. Explicaremos esto más a fondo en un tutorial posterior. Por ahora, queremos prestar atención a la clave accounts en el IDL y cómo se relaciona con el struct definido en el programa.
La clave accounts del IDL
A continuación, mostramos una captura de pantalla del IDL de nuestro programa anterior. Para que podamos ver la relación entre “Accounts” en ese atributo de Rust #[derive(Accounts)] y la clave “accounts” en el IDL:

En nuestro ejemplo, la clave accounts en el IDL JSON anterior, marcada por la flecha morada, está vacía. Pero este no es el caso para la mayoría de las transacciones útiles en Solana, como aprenderemos más adelante.
Dado que nuestro account struct para BoatyMcBoatface está vacío, la lista accounts en el IDL también está vacía.
Ahora veamos qué sucede cuando el struct no está vacío. Copia el código a continuación y reemplaza el contenido de lib.rs.
use anchor_lang::prelude::*;
declare_id!("8PSAL9t1RMb7BcewhsSFrRQDq61Y7YXC5kHUxMk5b39Z");
#[program]
pub mod anchor_function_tutorial {
use super::*;
pub fn non_empty_account_example(ctx: Context<NonEmptyAccountExample>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct NonEmptyAccountExample<'info> {
signer: Signer<'info>,
another_signer: Signer<'info>,
}
Ahora ejecuta anchor build - veamos qué obtenemos de vuelta en el nuevo IDL.
{
"version": "0.1.0",
"name": "anchor_function_tutorial",
"instructions": [
{
"name": "nonEmptyAccountExample",
"accounts": [
{
"name": "signer",
"isMut": false,
"isSigner": true
},
{
"name": "anotherSigner",
"isMut": false,
"isSigner": true
}
],
"args": []
}
],
"metadata": {
"address": "8PSAL9t1RMb7BcewhsSFrRQDq61Y7YXC5kHUxMk5b39Z"
}
}
Nota que “accounts” ya no está vacío y se ha poblado con los campos del struct: “signer” y “anotherSigner” (nota que another_signer se transformó de snake case a camel case). El IDL se ha actualizado para coincidir con el struct que acabamos de cambiar, específicamente con el número de cuentas que añadimos.
Profundizaremos más en el “Signer” en un próximo tutorial, pero por ahora puedes pensar en ello como algo análogo a tx.origin en Ethereum.
Un segundo ejemplo de un programa y un IDL.
Para resumir todo lo que hemos aprendido hasta ahora, vamos a crear otro programa con diferentes funciones y structs de Account.
use anchor_lang::prelude::*;
declare_id!("8PSAL9t1RMb7BcewhsSFrRQDq61Y7YXC5kHUxMk5b39Z");
#[program]
pub mod anchor_function_tutorial {
use super::*;
pub fn function_a(ctx: Context<NonEmptyAccountExample>) -> Result<()> {
Ok(())
}
pub fn function_b(ctx: Context<Empty>, firstArg: u64) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct NonEmptyAccountExample<'info> {
signer: Signer<'info>,
another_signer: Signer<'info>,
}
#[derive(Accounts)]
pub struct Empty {}
Ahora compílalo con anchor build
Miremos nuevamente el archivo IDL target/idl/anchor_function_tutorial.json y coloquemos estos archivos uno al lado del otro:

¿Puedes ver la relación entre el archivo IDL y el programa anterior?
La función function_a no tiene argumentos y esto se muestra en el IDL como un array vacío bajo la clave args.
Su Context toma el struct NonEmptyAccountExample. Este struct NonEmptyAccountExample tiene dos campos signer: signer y another_signer. Nota que estos se repiten como elementos en la clave account del IDL para function_a. Puedes ver que Anchor tradujo el snake case de Rust a camel case en el IDL.
Actualización para Anchor 0.30 Anchor ya no realiza esta traducción automáticamente (notas de la versión).
La función function_b toma un argumento u64. Su context struct está vacío, por lo que la clave accounts en el IDL para function_b es un array vacío.
En general, esperamos que el array de elementos en la clave accounts del IDL coincida con las claves del account struct que la función recibe en su argumento ctx.
Resumen
En este capítulo:
- Aprendimos que Solana utiliza un IDL (Interface Definition Language) para mostrar cómo interactuar con un programa de Solana y qué campos aparecen en el IDL.
- Introdujimos el struct modificado por
#[derive(Accounts)]y cómo se relaciona con los argumentos de la función. - Anchor interpreta las funciones snake_case en Rust como funciones camelCase en las pruebas de Typescript.
Publicado originalmente el 10 de febrero de 2024