DeFi 协议集成

DeFi 协议集成

DeFi(去中心化金融)是 Web3 最具革命性的应用场景之一。理解如何与 DeFi 协议交互,是从代币开发者成长为 DeFi 开发者的关键一步。

在 Solana 生态中,主流的 DeFi 协议包括 Raydium、Orca、Marinade、Jupiter 等。这些协议的核心都是对代币的各种操作——交换、质押、借贷。掌握了 SPL Token 的基础,你就拥有了理解这些协议的钥匙。

本章将通过简化的示例,展示与 AMM(自动做市商)和借贷协议交互的基本模式。真实的协议实现会更复杂,但核心原理是相通的。

与 AMM(自动做市商)交互

AMM 是去中心化交易所的核心机制。与传统的订单簿模式不同,AMM 使用数学公式来确定资产价格,允许用户随时进行交易而无需等待对手方。

最经典的 AMM 模型是恒定乘积做市商(Constant Product Market Maker),其核心公式是 x * y = k,其中 x 和 y 分别是池子中两种代币的数量,k 是一个常数。

以下示例展示如何与 Raydium 风格的 AMM 进行代币交换的基本结构:

rust
use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount}; // 简化的 swap 接口 #[derive(Accounts)] pub struct SwapTokens<'info> { pub user: Signer<'info>, #[account(mut)] pub user_token_in: Account<'info, TokenAccount>, #[account(mut)] pub user_token_out: Account<'info, TokenAccount>, #[account(mut)] pub pool_token_in: Account<'info, TokenAccount>, #[account(mut)] pub pool_token_out: Account<'info, TokenAccount>, /// CHECK: AMM 程序 pub amm_program: AccountInfo<'info>, pub token_program: Program<'info, Token>, } pub fn calculate_swap_output( amount_in: u64, reserve_in: u64, reserve_out: u64, fee_numerator: u64, fee_denominator: u64, ) -> u64 { // 扣除手续费后的输入量 let amount_in_with_fee = (amount_in as u128) .checked_mul((fee_denominator - fee_numerator) as u128) .unwrap(); // 恒定乘积公式: x * y = k // amount_out = (amount_in_with_fee * reserve_out) / (reserve_in * fee_denominator + amount_in_with_fee) let numerator = amount_in_with_fee .checked_mul(reserve_out as u128) .unwrap(); let denominator = (reserve_in as u128) .checked_mul(fee_denominator as u128) .unwrap() .checked_add(amount_in_with_fee) .unwrap(); (numerator / denominator) as u64 }

与借贷协议交互

借贷协议是 DeFi 的另一个核心组件。它允许用户存入资产赚取利息,或者抵押资产借出其他资产。Solana 上的 Solend、MarginFi 等协议都遵循类似的设计模式。

借贷协议的核心概念包括:

  • 存款池:用户存入的资产被汇集到一个池子中
  • 利率模型:根据资金利用率动态调整借贷利率
  • 抵押因子:决定用户可以借出多少资产的比例
  • 清算机制:当抵押率低于安全阈值时,允许清算人接管仓位

以下示例展示如何实现一个简化的存款功能:

rust
#[account] pub struct UserDeposit { pub user: Pubkey, pub mint: Pubkey, pub deposited_amount: u64, pub deposit_time: i64, pub bump: u8, } #[derive(Accounts)] pub struct Deposit<'info> { #[account(mut)] pub user: Signer<'info>, pub mint: Account<'info, Mint>, #[account( init_if_needed, payer = user, space = 8 + 32 + 32 + 8 + 8 + 1, seeds = [b"deposit", user.key().as_ref(), mint.key().as_ref()], bump, )] pub user_deposit: Account<'info, UserDeposit>, #[account( mut, token::mint = mint, token::authority = user, )] pub user_token_account: Account<'info, TokenAccount>, #[account( mut, seeds = [b"pool", mint.key().as_ref()], bump, )] pub pool_token_account: Account<'info, TokenAccount>, pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, } pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> { let clock = Clock::get()?; // 转移代币到池子 transfer( CpiContext::new( ctx.accounts.token_program.to_account_info(), Transfer { from: ctx.accounts.user_token_account.to_account_info(), to: ctx.accounts.pool_token_account.to_account_info(), authority: ctx.accounts.user.to_account_info(), }, ), amount, )?; // 更新用户存款记录 let deposit = &mut ctx.accounts.user_deposit; deposit.user = ctx.accounts.user.key(); deposit.mint = ctx.accounts.mint.key(); deposit.deposited_amount = deposit.deposited_amount.checked_add(amount).unwrap(); deposit.deposit_time = clock.unix_timestamp; deposit.bump = ctx.bumps.user_deposit; msg!("用户 {} 存入 {} 代币", ctx.accounts.user.key(), amount); Ok(()) }

补充:安全最佳实践

在构建涉及代币操作的程序时,安全性是第一优先级。一个小小的漏洞可能导致巨额资金损失。以下是一些经过实践检验的安全最佳实践。

权限验证

永远不要假设调用者的身份。每个涉及权限的操作都应该有明确的验证。

rust
// 不安全的做法 - 缺少权限验证 pub fn unsafe_mint(ctx: Context<UnsafeMint>, amount: u64) -> Result<()> { // 直接铸造,没有检查调用者是否有权限 mint_to(...)?; Ok(()) } // 安全的做法 - 使用 Anchor 约束进行验证 #[derive(Accounts)] pub struct SafeMint<'info> { #[account( mut, seeds = [b"config", mint.key().as_ref()], bump = config.bump, has_one = authority @ TokenError::InvalidAuthority, // 自动验证 )] pub config: Account<'info, TokenConfig>, pub authority: Signer<'info>, // 必须签名 // ... }

数值溢出保护

Rust 在 release 模式下默认不检查整数溢出,这可能导致严重的安全问题。

rust
// 不安全 - 可能溢出 let total = amount1 + amount2; // 安全 - 使用 checked 方法 let total = amount1.checked_add(amount2) .ok_or(TokenError::Overflow)?; // 或使用 saturating 方法(溢出时返回最大值) let total = amount1.saturating_add(amount2);

重入攻击防护

虽然 Solana 的执行模型使得传统的重入攻击更难实施,但在进行 CPI 调用时仍需谨慎。

rust
// 推荐的模式:先更新状态,再进行外部调用 pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> { let user_deposit = &mut ctx.accounts.user_deposit; // 1. 先检查 require!(user_deposit.amount >= amount, TokenError::InsufficientFunds); // 2. 再更新状态 user_deposit.amount = user_deposit.amount.checked_sub(amount).unwrap(); // 3. 最后执行转账 transfer(...)?; Ok(()) }

账户验证清单

在编写每个指令时,对照以下清单检查你的账户验证:

  1. 所有权验证:账户的 owner 是否正确?
  2. PDA 验证:PDA 的 seeds 是否包含所有必要的信息?
  3. Mint 匹配:涉及的 Token Account 是否都对应同一个 Mint?
  4. 签名验证:所有需要授权的操作是否都有正确的签名者?
  5. 余额检查:转账前是否验证了足够的余额?
  6. 状态检查:账户是否处于正确的状态(如未被冻结)?
RUSTPlayground
EDITOR ACTIVE
Initializing RUST Environment...

AMM 交易模拟器 (XY=K)

Swap
You Pay
SOL
You Receive
198.0198
USDC
Price Impact0.99%
Current Price1 SOL = 20.00 USDC

AMM Liquidity Pool

Constant Product: K = 2.00e+7
SOL Reserve: 1,000
USDC Reserve: 20,000
SOL Reserve →
USDC Reserve →