Skip to main content

使用Anchor PDA进行构建

在深入讨论CPI之前,让我们展示一下这些PDA的魅力吧!🎸

我们将利用Anchor框架创建一个电影评论程序。

该程序将让用户能够:

  • 使用PDA初始化一个新的电影评论账户,用于存放评论
  • 更新现有电影评论账户中的内容
  • 关闭现有的电影评论账户

设置流程

请访问https://beta.solpg.io/,如果你还没有SolPG钱包,请按照提示创建一个。然后,将lib.rs中的默认代码替换为以下内容:

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod movie_review {
use super::*;

}

🎥 电影账户状态(MovieAccountState)

我们首先要做的是定义State账户。

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod movie_review {
use super::*;

}

#[account]
pub struct MovieAccountState {
pub reviewer: Pubkey, // 评论者
pub rating: u8, // 评分
pub title: String, // 标题
pub description: String, // 描述
}

每个电影评论账户将包含以下信息:

  • reviewer - 进行评论的用户
  • rating - 对电影的评分
  • title - 电影的标题
  • description - 评论的具体内容

到现在为止,一切都相当简洁明了!

🎬 添加电影评论

感谢 Anchor 的便利性,我们可以轻松跳过所有的验证和安全检查,直接添加add_movie_review功能:

#[program]
pub mod movie_review{
use super::*;

pub fn add_movie_review(
ctx: Context<AddMovieReview>,
title: String,
description: String,
rating: u8,
) -> Result<()> {
msg!("创建了电影评论账户");
msg!("标题:{}", title);
msg!("描述:{}", description);
msg!("评分:{}", rating);

let movie_review = &mut ctx.accounts.movie_review;
movie_review.reviewer = ctx.accounts.initializer.key();
movie_review.title = title;
movie_review.rating = rating;
movie_review.description = description;
Ok(())
}
}

...

这些操作对你应该不陌生——这只是我们构建的本地电影评论程序的精简版。

现在,让我们为此添加Context

#[program]
pub mod movie_review {
use super::*;

...
}

#[derive(Accounts)]
#[instruction(title: String, description: String)]
pub struct AddMovieReview<'info> {
#[account(
init,
seeds = [title.as_bytes(), initializer.key().as_ref()],
bump,
payer = initializer,
space = 8 + 32 + 1 + 4 + title.len() + 4 + description.len()
)]
pub movie_review: Account<'info, MovieAccountState>,
#[account(mut)]
pub initializer: Signer<'info>,
pub system_program: Program<'info, System>,
}

...

再次强调,我们正以与本地操作完全相同的方式进行操作,但这次我们可以借助Anchor的力量。

我们正在使用两个seeds来初始化一个新的movie_review账户:

  • title - 指令数据中的电影标题
  • initializer.key() - 创建电影评论的initializer的公钥

此外,我们还根据space账户类型的结构将资金分配到新账户中。

🎞 更新电影评论

没有必要对这个小程序进行测试,我们可以直接完成它!下面是更新函数的代码示例:

#[program]
pub mod movie_review {
use super::*;

...

pub fn update_movie_review(
ctx: Context<UpdateMovieReview>,
title: String,
description: String,
rating: u8,
) -> Result<()> {
msg!("正在更新电影评论账户");
msg!("标题:{}", title);
msg!("描述:{}", description);
msg!("评分:{}", rating);

let movie_review = &mut ctx.accounts.movie_review;
movie_review.rating = rating;
movie_review.description = description;

return Ok(());
}

}

...

数据参数与add_movie_review相同,主要区别在于我们传入的Context。现在我们来定义它:

#[program]
pub mod movie_review {
use super::*;

...
}

#[derive(Accounts)]
#[instruction(title: String, description: String)]
pub struct UpdateMovieReview<'info> {
#[account(
mut,
seeds = [title.as_bytes(), initializer.key().as_ref()],
bump,
realloc = 8 + 32 + 1 + 4 + title.len() + 4 + description.len(),
realloc::payer = initializer,
realloc::zero = true,
)]
pub movie_review: Account<'info, MovieAccountState>,
#[account(mut)]
pub initializer: Signer<'info>,
pub system_program: Program<'info, System>,
}

...

我们使用seedsbump约束来验证movie_review账户。由于可能会有空间的变化,所以我们使用了realloc约束,让Anchor根据更新后的描述长度来自动处理账户空间和租金的重新分配。

realloc::payer约束规定了所需的额外lamports将来自或发送到初始化账户。

realloc::zero约束被设置为true,这是因为movie_review账户可能会多次更新,无论是缩小还是扩大分配给该账户的空间都可以灵活应对。

❌ 关闭电影评论

最后一部分是实现close指令,用以关闭已存在的movie_review账户。我们只需要Context类型的Close,不需要其他任何数据!

#[program]
pub mod movie_review {
use super::*;

...

pub fn close(_ctx: Context<Close>) -> Result<()> {
Ok(())
}

}

...

关于这个的Context定义:

#[program]
pub mod movie_review {
use super::*;

...
}

#[derive(Accounts)]
pub struct Close<'info> {
#[account(mut, close = reviewer, has_one = reviewer)]
movie_review: Account<'info, MovieAccountState>,
#[account(mut)]
reviewer: Signer<'info>,
}

...

我们使用close约束来指明我们要关闭的是movie_review账户,并且租金应退还到reviewer账户。

has_one约束用于限制关闭账户操作 - reviewer账户必须与电影评论账户上的reviewer相匹配。

我们完成了!试一下,它应该会像之前的本地电影评论程序一样运行。如果有任何问题,你可以与此处的解决方案代码进行对比 :)

🚢 挑战(这部分内容和build with solana Framework的内容重复了)

现在轮到你亲自构建一些内容了。由于我们从一个非常简单的程序开始,你所创建的程序将与我们刚刚创建的程序几乎完全相同。请尽量不要在这里复制粘贴,努力达到能够独立编写代码的程度。

  • 编写一个新程序,初始化一个counter账户,并使用传入指令数据参数来设置count字段。
  • 执行initializeincrementdecrement指令。
  • 按照我们在演示中的做法,为每个指令编写测试。
  • 使用anchor deploy来部署你的程序。如果你愿意,你可以像之前那样编写一个脚本来发送交易到你新部署的程序,然后使用Solana Explorer来查看程序日志。

像往常一样,对这些挑战充满创意,超越基本指示,如果你愿意,可以发挥你的想象力!

如果可能的话,请尽量独立完成这个任务!但如果遇到困难,你可以参考这个存储库solution-decrement分支。