En nuestro tutorial anterior, discutimos cómo inicializar una cuenta para poder persistir datos en el almacenamiento. Este tutorial muestra cómo escribir en una cuenta que ya hemos inicializado.
A continuación se muestra el código del tutorial anterior sobre la inicialización de cuentas de Solana. Hemos añadido una función set() para almacenar un número en MyStorage y el struct Set asociado.
El resto del código se mantiene sin cambios:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("GLKUcCtHx6nkuDLTz5TNFrR4tt4wDNuk24Aid2GrDLC6");
#[program]
pub mod basic_storage {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
// ****************************
// *** THIS FUNCTION IS NEW ***
// ****************************
pub fn set(ctx: Context<Set>, new_x: u64) -> Result<()> {
ctx.accounts.my_storage.x = new_x;
Ok(())
}
}
// **************************
// *** THIS STRUCT IS NEW ***
// **************************
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut, seeds = [], bump)]
pub my_storage: Account<'info, MyStorage>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space=size_of::<MyStorage>() + 8,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyStorage {
x: u64,
}
Ejercicio: Modifica la prueba para llamar a set() con el argumento 170. Este es el valor para x en MyStorage que estamos intentando persistir. Necesitas llamar a set() después de initialize(). No olvides convertir 170 en un bignumber.
Explicación de la función set()
A continuación, hemos reordenado ligeramente el código para mostrar la función set(), el struct Set y el struct MyStorage juntos:

Ahora explicamos cómo funciona ctx.accounts.my_storage.x = new_x:
- El campo
accounts(recuadro azul superior blue box) enctxnos da acceso a todas las claves en elstructSet. Esta no es la forma en que se listan las claves de unstructen Rust. La capacidad deaccountspara referirse a las claves en elstructSetse inserta mágicamente debido a la macro#[derive(Accounts)](recuadro azul inferior blue box). - La cuenta
my_storage(recuadro naranja) se configura comomuto mutable (recuadro verde) porque tenemos la intención de cambiar un valor en ella,x(recuadro rojo) - La clave
my_storage(recuadro naranja) nos da una referencia a la cuentaMyStorage(recuadro amarillo) al pasarMyStoragecomo un parámetro de genéricos aAccount. El hecho de que hayamos usado una clavemy_storagey unstructde almacenamientoMyStoragees por legibilidad, no necesitan ser variaciones en camel-case uno del otro. Lo que “los une” se ilustra con los recuadros amarillos y la flecha amarilla.
Esencialmente, cuando se llama a set(), el llamador (el cliente en Typescript) pasa la cuenta myStorage a set(). Dentro de esta cuenta está la dirección del almacenamiento. Entre bastidores, set cargará el almacenamiento, escribirá el nuevo valor de x, serializará el struct y luego lo volverá a almacenar.
El struct Set de Context
El struct Context para set() es considerablemente más simple que initialize porque solo necesita un recurso: una referencia mutable a la cuenta MyStorage.
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut, seeds = [], bump)]
pub my_storage: Account<'info, MyStorage>,
}
Recuerda, una transacción de Solana debe especificar de antemano a qué cuentas accederá. El struct para la función set() especifica que accederá de forma mutable (mut) a la cuenta my_storage.
Los parámetros seeds = [] y bump se utilizan para derivar la dirección de la cuenta que vamos a modificar. Aunque el usuario nos esté pasando la cuenta, Anchor valida que el usuario esté pasando una cuenta que este programa realmente posee volviendo a derivar la dirección y comparándola con lo que proporcionó el usuario.
El término bump puede tratarse como código repetitivo (boilerplate) por ahora. Pero para los curiosos, se utiliza para asegurar que la cuenta no sea una clave pública criptográficamente válida. Así es como el entorno de ejecución (runtime) sabe que esto se utilizará como almacenamiento de datos para los programas.
Aunque nuestro programa de Solana podría derivar la dirección de la cuenta de almacenamiento por sí mismo, el usuario aún necesita proporcionar la cuenta myStorage de todos modos. Esto es un requisito del entorno de ejecución de Solana por razones que discutiremos en un próximo tutorial.
Una forma alternativa de escribir la función set
Si estuviéramos escribiendo varias variables en la cuenta, sería bastante torpe seguir escribiendo ctx.accounts.my_storage una y otra vez de esta manera:
ctx.accounts.my_storage.x = new_x;
ctx.accounts.my_storage.y = new_y;
ctx.accounts.my_storage.z = new_z;
En su lugar, podemos usar una “referencia mutable” (&mut) de Rust que nos da un “manejador” (handle) sobre el valor para que lo manipulemos. Considera la siguiente reescritura de nuestra función set():
pub fn set(ctx: Context<Set>, new_x: u64) -> Result<()> {
let my_storage = &mut ctx.accounts.my_storage;
my_storage.x = new_x;
Ok(())
}
Ejercicio: Vuelve a ejecutar las pruebas con la nueva función set. No olvides reiniciar el validador si estás usando una testnet local.
Ver nuestra cuenta de almacenamiento
Si estás ejecutando un validador local para las pruebas, puedes ver los datos de la cuenta con la siguiente instrucción de línea de comandos de Solana:
# replace the address with the one in your test
solana account 9opwLZhoPdEh12DYpksnSmKQ4HTPSAmMVnRZKymMfGvn
Reemplaza la dirección con la que se imprimió en la consola desde las pruebas unitarias.
La salida es la siguiente:

Los primeros 8 bytes (recuadro verde) son el discriminador. Nuestra prueba almacenó el número 170 en el struct, el cual tiene una representación hexadecimal de aa que se muestra en el recuadro rojo.
Por supuesto, la línea de comandos no es el mecanismo que queremos usar para ver los datos de la cuenta en el frontend, o si queremos que nuestro programa vea la cuenta de otro programa. Esto se discutirá en el siguiente tutorial.
Ver nuestra cuenta de almacenamiento desde dentro del programa en Rust
Sin embargo, leer nuestro propio valor de almacenamiento dentro del programa en Rust es sencillo.
Añadimos la siguiente función a pub mod basic_storage:
pub fn print_x(ctx: Context<PrintX>) -> Result<()> {
let x = ctx.accounts.my_storage.x;
msg!("The value of x is {}", x);
Ok(())
}
y luego añadimos el siguiente struct para PrintX
#[derive(Accounts)]
pub struct PrintX<'info> {
pub my_storage: Account<'info, MyStorage>,
}
Ten en cuenta que my_storage no tiene la macro #[account(mut)] porque no necesitamos que sea mutable, solo la estamos leyendo.
Luego añadimos la siguiente línea a nuestra prueba:
await program.methods.printX().accounts({myStorage: myStorage}).rpc();
Si estás ejecutando los solana logs en segundo plano, deberías ver cómo se imprime el número.
Ejercicio: Escribe una función de incremento que lea x y almacene x + 1 nuevamente en x.
Publicado originalmente el 25 de febrero de 2024