Skip to main content

💸 使用Anchor赎回

lib.rs文件中找到Redeem结构体。由于它与Stake非常相似,我们可以直接粘贴该代码,并根据需要进行调整。

我们不需要的是nft_mintnft_editionprogram_authority。我们要更改nft_token_account的约束条件,将令牌授权改为'user',因为我们并没有传入mint

对于stake_state账户,由于不再需要初始化,所以我们只要设定种子和bump,并使其可变化。我们还可以为其增加一些手动约束。

constraint = *user.key == stake_state.user_pubkey,
constraint = nft_token_account.key() == stake_state.token_account

接下来,我们要添加几个账户。其中一个是stake_mint,它需要可变。这是奖励铸币的账户。

#[account(mut)]
pub stake_mint: Account<'info, Mint>,

另一个是stake_authority,它将是另一个未经检查的账户,所以让我们添加这个检查。

#[account(seeds = ["mint".as_bytes().as_ref()], bump)]

用户的user_stake_ata是一个TokenAccount,具有以下限制条件。

#[account(
init_if_needed,
payer=user,
associated_token::mint=stake_mint,
associated_token::authority=user
)]
pub user_stake_ata: Account<'info, TokenAccount>,

关联的 associated_token_program 是一个 AssociatedToken

pub associated_token_program: Program<'info, AssociatedToken>,

最后,将metadata_program替换为rent

pub rent: Sysvar<'info, Rent>,

然后,将我们的账户总数增加到10个。以下是所有代码的片段。

#[derive(Accounts)]
pub struct Redeem<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
mut,
token::authority=user
)]
pub nft_token_account: Account<'info, TokenAccount>,
#[account(
mut,
seeds = [user.key().as_ref(), nft_token_account.key().as_ref()],
bump,
constraint = *user.key == stake_state.user_pubkey,
constraint = nft_token_account.key() == stake_state.token_account
)]
pub stake_state: Account<'info, UserStakeInfo>,
#[account(mut)]
pub stake_mint: Account<'info, Mint>,
/// CHECK: manual check
#[account(seeds = ["mint".as_bytes().as_ref()], bump)]
pub stake_authority: UncheckedAccount<'info>,
#[account(
init_if_needed,
payer=user,
associated_token::mint=stake_mint,
associated_token::authority=user
)]
pub user_stake_ata: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
}

回到测试文件中,编写一个简单的测试以确保函数被触发。

it("Redeems", async () => {
await program.methods
.redeem()
.accounts({
nftTokenAccount: nft.tokenAddress,
stakeMint: mint,
userStakeAta: tokenAddress,
})
.rpc()

...然后运行 anchor test ,如果一切正常并且两个测试通过,我们就进入函数并编写赎回逻辑。

接下来,让我们进行一些检查,确认它是否已初始化,以及确保它已经抵押。我们需要在文件底部为这两种情况增加自定义错误。

require!(
ctx.accounts.stake_state.is_initialized,
StakeError::UninitializedAccount
);

require!(
ctx.accounts.stake_state.stake_state == StakeState::Staked,
StakeError::InvalidStakeState
);

...

#[msg("State account is uninitialized")]
UninitializedAccount,

#[msg("Stake state is invalid")]
InvalidStakeState,

之后,让我们获取时钟。

let clock = Clock::get()?;

现在,我们可以添加一些消息来跟踪事物的进展,并声明我们的时间和兑换金额。

msg!(
"Stake last redeem: {:?}",
ctx.accounts.stake_state.last_stake_redeem
);

msg!("Current time: {:?}", clock.unix_timestamp);
let unix_time = clock.unix_timestamp - ctx.accounts.stake_state.last_stake_redeem;
msg!("Seconds since last redeem: {}", unix_time);
let redeem_amount = (10 * i64::pow(10, 2) * unix_time) / (24 * 60 * 60);
msg!("Elligible redeem amount: {}", redeem_amount);

好了,现在我们将实际铸造奖励。首先,我们要使用我们的程序创建CpiContext,然后在MintTo对象中传递账户信息。最后,添加种子和金额。

msg!("Minting staking rewards");
token::mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.stake_mint.to_account_info(),
to: ctx.accounts.user_stake_ata.to_account_info(),
authority: ctx.accounts.stake_authority.to_account_info(),
},
&[&[
b"mint".as_ref(),
&[*ctx.bumps.get("stake_authority").unwrap()],
]],
),
redeem_amount.try_into().unwrap(),
)?;

一切准备就绪后,我们需要设置最后的赎回时间。如果不设置,用户可能会获得比实际应得的更多奖励。

ctx.accounts.stake_state.last_stake_redeem = clock.unix_timestamp;
msg!(
"Updated last stake redeem time: {:?}",
ctx.accounts.stake_state.last_stake_redeem
);

重新进入兑换测试,并添加以下内容。

const account = await program.account.userStakeInfo.fetch(stakeStatePda)
expect(account.stakeState === "Unstaked")
const tokenAccount = await getAccount(provider.connection, tokenAddress)

你可以继续添加更多的测试来确保其稳定性。目前我们只想先确保基本功能的实现和测试。假如一切顺利,我们可以继续进行解除质押的指令。