Make
现在我们可以转到 make 指令,该指令位于 make.rs 中,并将执行以下操作:
初始化托管记录并存储所有条款。
创建金库(一个由
escrow拥有的mint_a的关联代币账户 (ATA))。使用 CPI 调用 SPL-Token 程序,将创建者的 Token A 转移到该金库中。
账户
在此上下文中需要的账户包括:
maker:决定条款并将mint_a存入Escrow的用户escrow:持有交换条款(创建者、代币铸造、数量)的账户mint_a:maker存入的代币mint_b:maker想要交换的代币maker_ata_a:与maker和mint_a关联的代币账户,用于将代币存入vaultvault:与escrow和mint_a关联的代币账户,用于存放存入的代币associated_token_program:用于创建关联代币账户的关联代币程序token_program:用于 CPI 转账的代币程序system_program:用于创建Escrow的系统程序
结合所有约束条件,它看起来会是这样的:
#[derive(Accounts)]
#[instruction(seed: u64)]
pub struct Make<'info> {
#[account(mut)]
pub maker: Signer<'info>,
#[account(
init,
payer = maker,
space = Escrow::INIT_SPACE + Escrow::DISCRIMINATOR.len(),
seeds = [b"escrow", maker.key().as_ref(), seed.to_le_bytes().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,
/// Token Accounts
#[account(
mint::token_program = token_program
)]
pub mint_a: InterfaceAccount<'info, Mint>,
#[account(
mint::token_program = token_program
)]
pub mint_b: InterfaceAccount<'info, Mint>,
#[account(
mut,
associated_token::mint = mint_a,
associated_token::authority = maker,
associated_token::token_program = token_program
)]
pub maker_ata_a: InterfaceAccount<'info, TokenAccount>,
#[account(
init,
payer = maker,
associated_token::mint = mint_a,
associated_token::authority = escrow,
associated_token::token_program = token_program
)]
pub vault: InterfaceAccount<'info, TokenAccount>,
/// Programs
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}注意:此指令仅传递一个 token_program。由于 take 操作会转移两个代币铸造的代币,我们必须确保这两个代币铸造都由同一个程序(SPL Token 或 Token-2022)拥有,否则 CPI 将失败。
逻辑
初始化账户后,我们可以通过创建更小的辅助函数作为账户结构的实现,最终处理逻辑。
我们首先使用 set_inner() 辅助工具填充 Escrow,然后通过 transfer CPI 存入代币,如下所示:
impl<'info> Make<'info> {
/// # Create the Escrow
fn populate_escrow(&mut self, seed: u64, amount: u64, bump: u8) -> Result<()> {
self.escrow.set_inner(Escrow {
seed,
maker: self.maker.key(),
mint_a: self.mint_a.key(),
mint_b: self.mint_b.key(),
receive: amount,
bump,
});
Ok(())
}
/// # Deposit the tokens
fn deposit_tokens(&self, amount: u64) -> Result<()> {
transfer_checked(
CpiContext::new(
self.token_program.to_account_info(),
TransferChecked {
from: self.maker_ata_a.to_account_info(),
mint: self.mint_a.to_account_info(),
to: self.vault.to_account_info(),
authority: self.maker.to_account_info(),
},
),
amount,
self.mint_a.decimals,
)?;
Ok(())
}
}我们可以看到 Anchor 在多个方面为我们提供了帮助:
set_inner():确保每个字段都已填充。transfer_checked:像我们之前使用的系统辅助工具一样封装了 Token CPI。
现在我们可以继续创建一个 handler 函数,在使用辅助工具之前执行一些检查,如下所示:
pub fn handler(ctx: Context<Make>, seed: u64, receive: u64, amount: u64) -> Result<()> {
// Validate the amount
require_gt!(receive, 0, EscrowError::InvalidAmount);
require_gt!(amount, 0, EscrowError::InvalidAmount);
// Save the Escrow Data
ctx.accounts.populate_escrow(seed, receive, ctx.bumps.escrow)?;
// Deposit Tokens
ctx.accounts.deposit_tokens(amount)?;
Ok(())
}这里我们添加了两个验证检查;一个针对 amount,另一个针对 receive 参数,以确保我们不会为任一参数传递零值。
警告
SPL Token-2022 的某些扩展功能,例如转账钩子、保密转账、默认账户状态,可能会引入漏洞,例如阻止转账、锁定资金以及在托管逻辑、金库或 CPI 中导致资金被抽走。
确保
mint_a和mint_b由同一个代币程序拥有,以防止 CPI 失败。使用经过充分审计的代币(例如 USDC、wSOL)来自标准 SPL Token 程序。
避免使用未经验证或复杂的 Token-2022 铸币。