Skip to main content

使用Anchor CPIs构建

回归未来。我们将通过以CPIs结尾的电影评论来为这个话题画上完美的句号。

这一次,我们将:

  • 添加指令以创建带有元数据的代币铸造
  • 添加指令以添加评论
  • 在创建评论时铸造薄荷代币
  • 在添加评论时铸造薄荷代币

初始代码

  • 起始代码链接:这里
  • 我们将在之前的PDA演示基础上进行扩展

首先,我们来定义 create_reward_mint 指令:

pub fn create_reward_mint(
ctx: Context<CreateTokenReward>,
uri: String,
name: String,
symbol: String,
) -> Result<()> {
msg!("Create Reward Token");

let seeds = &["mint".as_bytes(), &[*ctx.bumps.get("reward_mint").unwrap()]];

let signer = [&seeds[..]];

let account_info = vec![
ctx.accounts.metadata.to_account_info(),
ctx.accounts.reward_mint.to_account_info(),
ctx.accounts.user.to_account_info(),
ctx.accounts.token_metadata_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.rent.to_account_info(),
];

invoke_signed(
&create_metadata_accounts_v2(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.metadata.key(),
ctx.accounts.reward_mint.key(),
ctx.accounts.reward_mint.key(),
ctx.accounts.user.key(),
ctx.accounts.user.key(),
name,
symbol,
uri,
None,
0,
true,
true,
None,
None,
),
account_info.as_slice(),
&signer,
)?;

Ok(())
}

尽管代码很长,但非常直观!我们正在为Token元数据程序创建一个CPI,用来指向 create_metadata_account_v2 指令。

接下来,我们会看到 CreateTokenReward 上下文类型。

有关 /// CHECK 的详细信息在这里:安全检查。

#[derive(Accounts)]
pub struct CreateTokenReward<'info> {
#[account(
init,
seeds = ["mint".as_bytes()],
bump,
payer = user,
mint::decimals = 6,
mint::authority = reward_mint,

)]
pub reward_mint: Account<'info, Mint>,

#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
pub token_program: Program<'info, Token>,

/// CHECK:
#[account(mut)]
pub metadata: AccountInfo<'info>,
/// CHECK:
pub token_metadata_program: AccountInfo<'info>,
}

创建错误代码(ErrorCode)

  • 用于检查评级的错误代码
  • Anchor已处理我们在原生版本中的其他检查)
#[error_code]
pub enum ErrorCode {
#[msg("评分大于5或小于1")]
InvalidRating,
}

更新 add_movie_review

  • 添加对ErrorCode的检查
  • 设置评论计数器账户
  • 通过CPImintTo 指令,将代币铸造给评论人
pub fn add_movie_review(
ctx: Context<AddMovieReview>,
title: String,
description: String,
rating: u8,
) -> Result<()> {
msg!("创建了影评账户");
msg!("标题:{}", title);
msg!("描述:{}", description);
msg!("评分:{}", rating);

if rating > 5 || rating < 1 {
msg!("评分不能高于5");
return err!(ErrorCode::InvalidRating);
}

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;

msg!("创建了影评计数器账户");
let movie_comment_counter = &mut ctx.accounts.movie_comment_counter;
movie_comment_counter.counter = 0;
msg!("计数器:{}", movie_comment_counter.counter);

let seeds = &["mint".as_bytes(), &[*ctx.bumps.get("reward_mint").unwrap()]];

let signer = [&seeds[..]];

let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token::MintTo {
mint: ctx.accounts.reward_mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.reward_mint.to_account_info(),
},
&signer,
);

token::mint_to(cpi_ctx, 10000000)?;
msg!("已铸币");
Ok(())
}

更新 AddMovieReview 上下文

  • 初始化 movie_review
  • 初始化 movie_comment_counter
  • 使用 init_if_needed 来初始化令牌账户
#[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(
init,
seeds = ["counter".as_bytes(), movie_review.key().as_ref()],
bump,
payer = initializer,
space = 8 + 8
)]
pub movie_comment_counter: Account<'info, MovieCommentCounter>,
#[account(mut,
seeds = ["mint".as_bytes()],
bump
)]
pub reward_mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = initializer,
associated_token::mint = reward_mint,
associated_token::authority = initializer
)]
pub token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub initializer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
}

ErrorCode 添加到 update_movie_review

  • update_movie_review 指令中添加 ErrorCode 检查
pub fn update_movie_review(
ctx: Context<UpdateMovieReview>,
title: String,
description: String,
rating: u8,
) -> Result<()> {
msg!("更新了影评账户");
msg!("标题:{}", title);
msg!("描述:{}", description);
msg!("评分:{}", rating);

if rating > 5 || rating < 1 {
msg!("评分不能高于5");
return err!(ErrorCode::InvalidRating);
}

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

Ok(())
}

添加 add_comment

  • 创建 add_comment 指令
  • 设置 movie_comment 数据
  • 通过 CPImintTo 指令,将代币铸造给审核者
pub fn add_comment(ctx: Context<AddComment>, comment: String) -> Result<()> {
msg!("已创建评论账户");
msg!("评论:{}", comment);

let movie_comment = &mut ctx.accounts.movie_comment;
let movie_comment_counter = &mut ctx.accounts.movie_comment_counter;

movie_comment.review = ctx.accounts.movie_review.key();
movie_comment.commenter = ctx.accounts.initializer.key();
movie_comment.comment = comment;
movie_comment.count = movie_comment_counter.counter;

movie_comment_counter.counter += 1;

let seeds = &["mint".as_bytes(), &[*ctx.bumps.get("reward_mint").unwrap()]];

let signer = [&seeds[..]];

let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token::MintTo {
mint: ctx.accounts.reward_mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.reward_mint.to_account_info(),
},
&signer,
);

token::mint_to(cpi_ctx, 5000000)?;
msg!("已铸造代币");

Ok(())
}

添加 AddComment 上下文

  • 初始化 movie_comment
  • 使用 init_if_needed 来初始化令牌账户
#[derive(Accounts)]
#[instruction(comment: String)]
pub struct AddComment<'info> {
#[account(
init,
seeds = [movie_review.key().as_ref(), &movie_comment_counter.counter.to_le_bytes()],
bump,
payer = initializer,
space = 8 + 32 + 32 + 4 + comment.len() + 8
)]
pub movie_comment: Account<'info, MovieComment>,
pub movie_review: Account<'info, MovieAccountState>,
#[account(
mut,
seeds = ["counter".as_bytes(), movie_review.key().as_ref()],
bump,
)]
pub movie_comment_counter: Account<'info, MovieCommentCounter>,
#[account(mut,
seeds = ["mint".as_bytes()],
bump
)]
pub reward_mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = initializer,
associated_token::mint = reward_mint,
associated_token::authority = initializer
)]
pub token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub initializer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
}

构建,部署,测试

解决方案:https://beta.solpg.io/6319c7bf77ea7f12846aee87

如果你使用自己的编辑器,你必须在 mpl-token-metadataCargo.toml 中添加 features = ["no-entrypoint"]

否则,将会出现以下错误:the #[global_allocator] in this crate conflicts with global allocator in: mpl_token_metadata

  • 构建和部署
  • 使用 SolPG 进行测试