Solana[part17]_Solana质押-编写应用级别的质押合约-解质押部分

AI-摘要
sonia33 GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Solana[part17]_Solana质押-编写应用级别的质押合约-解质押部分
SoniaChenSolana质押-编写应用级别的质押合约-解质押部分
核心代码地址提交:
https://github.com/SoniaChan33/anchor_social/commit/b6ca707c33992ef5e72d1be5e4b6edc14e385b98
1. 解质押功能核心逻辑
解质押是质押流程的反向操作,核心目标是将质押的 NFT 归还给用户,并销毁质押时获得的流动性代币。具体流程通过 nft_unstake 函数实现,步骤如下:
1.1 权限校验
函数首先验证解质押操作的合法性,确保:
- 解质押的 NFT 与质押记录中的 NFT 匹配(
stake_info.nft_mint_account == nft_mint_account.key()); - 发起解质押的用户是原质押人(
stake_info.staker == authority.key())。
若不满足,返回UnstakeError::NoAuthority错误。
// 权限校验代码(源自nft_unstake.rs)
require!(
&ctx.accounts.stake_info.nft_mint_account == &ctx.accounts.nft_mint_account.key(),
UnstakeError::NoAuthority,
);
require!(
&ctx.accounts.stake_info.staker == &ctx.accounts.authority.key(),
UnstakeError::NoAuthority,
);
1.2 NFT 转回用户账户
通过 transfer 操作将暂存于合约托管账户(program_receipt_ata)的 NFT 转回用户的关联账户(nft_associated_token_account)。
- 由于合约托管账户的权限归
stake_info(PDA 账户),需使用stake_info的种子(StakeInfo::SEED_PREFIX + nft_mint_account.key() + bump)生成签名,确保操作合法性。
// NFT 转移代码(源自nft_unstake.rs)
let nft_mint_account = ctx.accounts.nft_mint_account.key();
let signer_seeds: &[&[&[u8]]] = &[&[
StakeInfo::SEED_PREFIX.as_bytes(),
nft_mint_account.as_ref(),
&[ctx.bumps.stake_info],
]];
transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.program_receipt_ata.to_account_info(),
to: ctx.accounts.nft_associated_token_account.to_account_info(),
authority: ctx.accounts.stake_info.to_account_info(),
},
)
.with_signer(signer_seeds),
1, // NFT 数量固定为1
)?;
1.3 流动性代币销毁
用户解质押时,需销毁质押时获得的流动性代币(通过 burn 操作),数量通过 stake_info.salvage_value(10000) 计算(基于质押时长)。
// 代币销毁代码(源自nft_unstake.rs)
let amount = ctx.accounts.stake_info.salvage_value(10000);
burn(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Burn {
mint: ctx.accounts.token_mint_account.to_account_info(),
from: ctx.accounts.associated_token_account.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
amount,
)?;
2. 账户结构说明(NFTUnStake)
NFTUnStake 结构体定义了解质押所需的所有账户,各账户作用如下:
| 账户名 | 作用描述 |
|---|---|
stake_info |
存储质押记录(质押人、NFT 地址、质押时间),以 StakeInfo::SEED_PREFIX + NFT Mint 地址 为种子的 PDA 账户。 |
program_receipt_ata |
合约托管 NFT 的关联账户,权限归 stake_info,用于暂存质押的 NFT。 |
token_mint_account |
流动性代币的 Mint 账户,以 MyToken::SEED_PREFIX 为种子的 PDA 账户,用于销毁代币。 |
associated_token_account |
用户持有的流动性代币账户,用于销毁质押时获得的代币。 |
nft_mint_account |
被质押 NFT 的 Mint 账户,标识唯一 NFT。 |
nft_associated_token_account |
用户接收 NFT 的关联账户,解质押后 NFT 转回此处。 |
authority |
解质押发起者(原质押人),需签名授权操作。 |
系统程序(token_program 等) |
提供代币转移、销毁等基础功能的系统程序。 |
3. 利息计算逻辑(salvage_value 方法)
解质押时销毁的代币数量通过 StakeInfo::salvage_value 计算,逻辑为:基于质押时长(当前 Epoch 与质押时 Epoch 的差值),按每 Epoch 2% 的比例计算(原代码实现)。
// 利息计算代码(源自stake.rs)
pub fn salvage_value(&self, amount: u64) -> u64 {
let now = Clock::get().unwrap().epoch;
// 每一个epoch减2%(原代码逻辑)
let p = max(0, (now - self.staked_at) * 2) as f64 / 100.0;
(amount as f64 * p) as u64
}
4. 关键注意事项
4.1 利息计算的潜在问题
- 浮点数精度风险:原代码使用
f64计算比例(如(now - staked_at) * 2 as f64 / 100.0),可能导致精度丢失(例如10000 * 0.02理论为 200,但浮点数运算可能产生偏差)。 - Epoch 差值为 0 的问题:若测试网络中 Epoch 长期未更新(默认配置
slots-per-epoch较大),now - self.staked_at为 0,导致salvage_value返回 0,用户无实际获利。
4.2 测试网络配置建议
为解决 Epoch 不更新的问题,启动本地测试网时需手动设置较小的 slots-per-epoch,确保 Epoch 能快速推进:
# 每32个slot为1个Epoch(加速Epoch更新,便于测试利息计算)
solana-test-validator --slots-per-epoch 32 -r \
--bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s metadata.so
可通过以下命令验证 Epoch 状态:
solana epoch-info --url localhost # 查看当前Epoch和slot数
4.3 权限与安全性
- 解质押必须验证
stake_info中的质押人与发起者一致,防止非所有者操作。 - NFT 转移时需使用
stake_info的 PDA 签名(signer_seeds),确保只有合约有权限操作托管的 NFT,避免权限泄露。
总结
解质押功能通过权限校验、NFT 转回、流动性代币销毁完成闭环,核心依赖 stake_info 记录的质押关系和 salvage_value 计算的销毁数量。需特别注意浮点数计算精度和测试网 Epoch 配置,以确保用户获利逻辑可正常验证。







