Write to storage
In our previous tutorial, we've discussed how to initialize an account so that we could persis data in storage. Now let's discuss how to read and write to an account.
Try it out
anchor new day_17
let's add an extra function set()
to the previous code, the rest remains unchanged:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("AoiMpugaS2QZJP38Wvrcy3KVDFNayy4oPV3TZDnFB3ns");
#[program]
pub mod day_17 {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
// new code
pub fn set(ctx: Context<Set>, x: u64) -> Result<()> {
ctx.accounts.my_storage_set.x = x;
Ok(())
}
}
// new code
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut, seeds=[], bump)]
pub my_storage_set: Account<'info, MyStorage>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer=signer, space=size_of::<MyStorage>()+8,seeds=[], bump)]
pub my_storage_account: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyStorage {
x: u64,
}
How function Set() works
Below, we have slightly reordered the code to show the set() function, the Set struct and the MyStorage struct close together.
when function set() is called, the myStorage
account will be passed to my_storage_set
, and function set will load the storage, write the new value of x, serialize the struct, then store it back.
- mut: indicates this account can be modified.
- seeds and bump: are used to derive the address of the account we will be modifying.
let's update the test case, call set
after account initialization:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Day17 } from '../target/types/day_17';
describe("day_17", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Day17 as Program<Day17>;
it("should initialized!", async () => {
// same to previous
// account initialization
// call set()
const value = new anchor.BN(200)
const tx = await program.methods.set(value).accounts({
myStorageAccount: myStorage
}).rpc();
console.log('tx: ', tx);
})
})
it works as expected, we will fetch x later on.
myStorage account: J2Xy2SHhchzvK7drEGcM2FnL3xTeSEKMQNf3NcAyE3p3
Viewing account outside program
We can view the account data with the following Solana command line instruction:
# solana account <MyStorage address>
solana account J2Xy2SHhchzvK7drEGcM2FnL3xTeSEKMQNf3NcAyE3p3
The first 8 bytes 1c f2 3b 85 43 19 31
before c8
are the discriminator, we can ignore as for now.
Viewing account inside program
Reading our own storage value inside the Rust program however, is straightforward.
We add the following function
pub fn get(ctx: Context<Get>) -> Result<()> {
let x = ctx.accounts.my_storage_get.x;
msg!("Value of x: {}", x);
Ok(())
}
add new Get Account
#[derive(Accounts)]
pub struct Get<'info> {
pub my_storage_get: Account<'info, MyStorage>,
}
Note that we don't use mut
in macro cos it's read only, let update the test case and test
it.only("should get value", async () => {
const seeds = []
const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
await program.methods.get().accounts({
myStorageGet: myStorage
}).rpc();
})
get 200 as expected!
cool!
Key takeaways
- don't forget to pass myStorage account during the call
Links
- original article: https://www.rareskills.io/post/solana-counter-program
- source code: https://github.com/dukedaily/solana-expert-code/tree/day_17