区块链中文技术社区

Solana基础 - Solana 计数器教程:在账户中读写数据

本教程详细介绍了如何在Anchor框架下向已初始化的Solana账户写入数据,并解释了相关代码的实现原理。

在之前的教程中,我们讨论了如何初始化账户以便将数据持久化存储。本教程将展示如何向我们已初始化的账户写入数据。

以下是之前关于初始化 Solana 账户的教程中的代码。我们添加了一个 set() 函数,用于在 MyStorage 中存储一个数字,并添加了相关的 Set 结构体。

其余代码保持不变:

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(())
    }

    // ****************************
    // *** 这个函数是新添加的 ***
    // ****************************
    pub fn set(ctx: Context<Set>, new_x: u64) -> Result<()> {
        ctx.accounts.my_storage.x = new_x;
        Ok(())
    }
}

// **************************
// *** 这个结构体是新添加的 ***
// **************************
##[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,
}

练习:修改测试,使用参数 170 调用 set()。这是我们尝试持久化存储在 MyStorage 中的 x 的值。你需要在 initialize() 之后调用 set()。别忘了将 170 转换为大数。

set() 函数解释

下面,我们稍微重新排列了代码,将 set() 函数、Set 结构体和 MyStorage 结构体放在一起:我们现在解释 ctx.accounts.my_storage.x = new_x 的工作原理:

本质上,当调用 set() 时,调用者(Typescript 客户端)将 myStorage 账户传递给 set()。在这个账户内部是存储的地址。在幕后,set 将加载存储,写入 x 的新值,序列化结构体,然后将其存储回去。

SetContext 结构体

set()Context 结构体比 initialize 简单得多,因为它只需要一个资源:对 MyStorage 账户的可变引用。

##[derive(Accounts)]
pub struct Set<'info> {
    #[account(mut, seeds = [], bump)]
    pub my_storage: Account<'info, MyStorage>,
}

回想一下,Solana 交易必须提前指定它将访问哪些账户。set() 函数的结构体指定它将可变地(mut)访问 my_storage 账户。

seeds = []bump 用于派生我们将修改的账户的地址。尽管用户正在为我们传递账户,但 Anchor 通过重新派生地址并将其与用户提供的内容进行比较来验证用户是否传递了该程序真正拥有的账户。

bump 这个术语现在可以视为样板代码。但对于好奇的人来说,它用于确保账户不是加密有效的公钥。这是运行时知道这将用于程序数据存储的方式。

即使我们的 Solana 程序可以自行派生存储账户的地址,用户仍然需要提供 myStorage 账户。这是 Solana 运行时要求的,原因我们将在接下来的教程中讨论。

编写 set 函数的另一种方式

如果我们要向账户写入多个变量,像这样反复写 ctx.accounts.my_storage 会相当笨拙:

ctx.accounts.my_storage.x = new_x;
ctx.accounts.my_storage.y = new_y;
ctx.accounts.my_storage.z = new_z;

相反,我们可以使用 Rust 中的“可变引用”(&mut),它为我们提供了一个“Handle”来操作值。考虑以下对 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(())
}

练习:使用新的 set 函数重新运行测试。如果你使用的是本地测试网,别忘了重置验证器。

查看我们的存储账户

如果你正在为测试运行本地验证器,你可以使用以下 Solana 命令行指令查看账户数据:

## 将地址替换为你的测试中的地址
solana account 9opwLZhoPdEh12DYpksnSmKQ4HTPSAmMVnRZKymMfGvn

将地址替换为单元测试中控制台记录的地址。

输出如下:前 8 个字节(绿色框)是判别器。我们的测试在结构体中存储了数字 170,其十六进制表示为 aa,如红色框所示。

当然,命令行不是我们在前端查看账户数据,或者如果我们希望我们的程序查看另一个程序的账户时想要使用的机制。这将在接下来的教程中讨论。

从 Rust 程序中查看我们的存储账户

然而,在 Rust 程序中读取我们自己的存储值非常简单。

我们向 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(())
}

然后我们为 PrintX 添加以下结构体:

##[derive(Accounts)]
pub struct PrintX<'info> {
    pub my_storage: Account<'info, MyStorage>,
}

请注意,my_storage 没有 #[account(mut)] 宏,因为我们不需要它是可变的,我们只是读取它。

然后我们向测试中添加以下行:

await program.methods.printX().accounts({myStorage: myStorage}).rpc();

如果你在后台运行 solana logs,你应该会看到数字被打印出来。

练习:编写一个增量函数,读取 x 并将 x + 1 存储回 x

转载:https://learnblockchain.cn/article/11488

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »