Anchor 闪电贷

指令内省是一项强大的功能,它允许区块链程序检查和分析同一交易包中的其他指令。这包括尚未执行的指令,使您的程序能够“前瞻性”地根据交易后续发生的情况做出决策。
可以将其想象为交易的“X光视野”:您的程序可以透视整个交易,了解完整的操作序列,然后再决定如何进行。
指令内省最引人注目的应用是闪电贷。这是一种仅存在于单笔交易范围内的独特贷款类型。
以下是闪电贷的工作原理:
借款:在交易开始时,您可以通过
loan指令即时借入大量资金使用:您可以在同一笔交易中使用这笔借来的资金进行交易、套利或其他操作
还款:在交易结束之前,您必须通过
repay指令归还贷款并支付少量费用
关键点在于,闪电贷依赖于区块链交易的原子性。如果交易的任何部分失败(包括还款),整个交易将被回滚,就像从未发生过一样。这意味着贷款方没有任何风险:要么他们得到还款,要么贷款根本不存在。
在本次挑战中,您将创建一个简单的闪电贷程序,展示指令内省的实际应用。该程序将检查同一交易中不同指令的指令数据和账户,以确保贷款条款得到满足。
如果您是指令自省的新手,我们建议您从指令自省课程开始,以了解本程序中使用的基本概念。
安装
在开始之前,请确保您已安装 Rust 和 Anchor。如果需要安装说明,请参考官方 Anchor 文档。然后在终端中运行:
anchor init blueshift_anchor_flash_loan添加所需的依赖项:
anchor-spl:提供用于处理 SPL 代币(Solana 的代币标准)的工具
cd blueshift_anchor_flash_loan
cargo add anchor-spl现在,您已经准备好开始构建您的闪电贷程序了!
模板
让我们通过设置基本结构、账户和错误处理来构建闪电贷程序的基础,这些将被借款和还款指令共同使用。
我们将在lib.rs中实现所有内容,因为我们只有两个共享相同账户结构的指令。以下是包含所有基本组件的起始模板:
use anchor_lang::prelude::*;
use anchor_spl::{
token::{Token, TokenAccount, Mint, Transfer, transfer},
associated_token::AssociatedToken
};
use anchor_lang::{
Discriminator,
solana_program::sysvar::instructions::{
ID as INSTRUCTIONS_SYSVAR_ID,
load_instruction_at_checked
}
};
declare_id!("22222222222222222222222222222222222222222222");
#[program]
pub mod blueshift_anchor_flash_loan {
use super::*;
pub fn borrow(ctx: Context<Loan>, borrow_amount: u64) -> Result<()> {
// borrow logic...
Ok(())
}
pub fn repay(ctx: Context<Loan>) -> Result<()> {
// repay logic...
Ok(())
}
}
#[derive(Accounts)]
pub struct Loan<'info> {
// loan accounts...
}
#[error_code]
pub enum ProtocolError {
// error enum..
}注意:请记得将程序 ID 更改为22222222222222222222222222222222222222222222,因为我们在底层使用它来测试您的程序。
账户
由于borrow和repay指令使用相同的账户,我们可以创建一个单一的Loan上下文来服务于这两个功能。这使我们的代码更易于维护和理解。
我们的Loan账户结构需要以下组件:
borrower:请求闪电贷的用户。protocol:拥有协议流动性池的程序派生地址(PDA)。mint:被借用的特定代币。borrower_ata:借款人的关联代币账户,用于接收借用的代币。protocol_ata:协议的关联代币账户,用于提供借用的代币。instructions:用于自省的指令 Sysvar 账户。token_program、associated_token_program和system_program:程序所需的其他程序。
以下是我们定义账户结构的方法:
#[derive(Accounts)]
pub struct Loan<'info> {
#[account(mut)]
pub borrower: Signer<'info>,
#[account(
seeds = [b"protocol".as_ref()],
bump,
)]
pub protocol: SystemAccount<'info>,
pub mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = borrower,
associated_token::mint = mint,
associated_token::authority = borrower,
)]
pub borrower_ata: Account<'info, TokenAccount>,
#[account(
mut,
associated_token::mint = mint,
associated_token::authority = protocol,
)]
pub protocol_ata: Account<'info, TokenAccount>,
#[account(address = INSTRUCTIONS_SYSVAR_ID)]
/// CHECK: InstructionsSysvar account
instructions: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>
}如您所见,此指令所需的账户及其约束条件非常直观:
protocol:使用seeds = [b"protocol".as_ref()]创建一个确定性的地址,该地址拥有所有协议流动性。这确保了只有我们的程序可以控制这些资金。borrower_ata:使用init_if_needed,因为借款人可能尚未为此特定代币创建关联的代币账户。如果需要,约束条件会自动创建一个。protocol_ata:必须已经存在并且是可变的,因为我们将从中转移代币。associated_token::authority = protocol约束条件确保只有协议PDA可以授权转移。instructions:使用address约束条件来验证我们正在访问包含交易指令数据的正确系统账户。
错误
闪电贷在多个步骤中需要精确验证,因此我们需要全面的错误处理。以下是完整的错误枚举:
#[error_code]
pub enum ProtocolError {
#[msg("Invalid instruction")]
InvalidIx,
#[msg("Invalid instruction index")]
InvalidInstructionIndex,
#[msg("Invalid amount")]
InvalidAmount,
#[msg("Not enough funds")]
NotEnoughFunds,
#[msg("Program Mismatch")]
ProgramMismatch,
#[msg("Invalid program")]
InvalidProgram,
#[msg("Invalid borrower ATA")]
InvalidBorrowerAta,
#[msg("Invalid protocol ATA")]
InvalidProtocolAta,
#[msg("Missing repay instruction")]
MissingRepayIx,
#[msg("Missing borrow instruction")]
MissingBorrowIx,
#[msg("Overflow")]
Overflow,
}有了这个基础,我们就可以准备实现闪电贷指令的核心逻辑了。账户结构确保了代币的正确处理,而错误系统为调试和安全验证提供了清晰的反馈。