LogoAnchor 中文文档

无依赖的组合性

学习如何使用 Anchor 的 declare_program 宏与程序交互 而无需额外的依赖。

declare_program!() 宏简化了与 Anchor 程序交互的过程,通过生成 Rust 模块(来自程序的 IDL),可用于链上和链下代码。你可以在 这里 找到示例程序。

以下模块由 declare_program!() 宏生成:

模块描述
cpi从其他链上程序入侵到该程序的跨程序调用(CPI)的辅助功能
client构建程序指令所需的账户和参数,以便添加到客户端交易中
account在程序中定义的账户数据类型(程序状态)
program用于识别程序的程序 ID 常量
constants在程序中定义的程序常量
events在程序中定义的程序事件
types在程序中定义的程序类型

示例

以下示例展示了如何在两种情况下使用 declare_program!() 宏:

  1. 从一个程序到另一个程序进行跨程序调用(CPI)
  2. 构建客户端交易以调用程序的指令

这两个示例显示了 declare_program!() 宏生成的模块如何简化程序交互,同样适用于链上或链下代码。

链上 CPI

要使用 declare_program!() 宏,你需要目标程序的 IDL 文件。IDL 文件必须放置在项目中的一个名为 /idls 的目录中。/idls 目录可以在项目结构中的任何级别。例如,你的项目可以有以下布局:

example.json
lib.rs
Cargo.toml

以下是目标(被调用)程序的源代码 (lib.rs),它生成上面显示的 example.json IDL 文件。

使用程序的 IDL 文件,另一个程序可以使用 declare_program!() 宏生成 CPI 模块,使其能够进行此程序指令的 CPI。

use anchor_lang::prelude::*;   declare_id!("8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8");   #[program] pub mod example {  use super::*;    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {  let counter = &ctx.accounts.counter;  msg!("创建了计数器账户!当前计数:{}", counter.count);  Ok(())  }    pub fn increment(ctx: Context<Increment>) -> Result<()> {  let counter = &mut ctx.accounts.counter;  msg!("之前的计数器:{}", counter.count);    counter.count += 1;  msg!("计数器已增加!当前计数:{}", counter.count);  Ok(())  } }   #[derive(Accounts)] pub struct Initialize<'info> {  #[account(mut)]  pub payer: Signer<'info>,    #[account(  init,  payer = payer,  space = 8 + 8  )]  pub counter: Account<'info, Counter>,  pub system_program: Program<'info, System>, }   #[derive(Accounts)] pub struct Increment<'info> {  #[account(mut)]  pub counter: Account<'info, Counter>, }   #[account] pub struct Counter {  pub count: u64, }

以下是调用程序 (example-cpi) 的源代码 (lib.rs),它使用 declare_program!() 宏生成一个 CPI 模块以调用上面被调用程序中定义的指令。

use anchor_lang::prelude::*;   declare_id!("GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH");     declare_program!(example); use example::{  accounts::Counter,  cpi::{  self,  accounts::{Increment, Initialize},  },  program::Example, };   #[program] pub mod example_cpi {    use super::*;    pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> {  // 为初始化创建 CPI 上下文  let cpi_ctx = CpiContext::new(  ctx.accounts.example_program.to_account_info(),  Initialize {  payer: ctx.accounts.payer.to_account_info(),  counter: ctx.accounts.counter.to_account_info(),  system_program: ctx.accounts.system_program.to_account_info(),  },  );    // 调用初始化指令  cpi::initialize(cpi_ctx)?;  Ok(())  }    pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> {  // 为增量创建 CPI 上下文  let cpi_ctx = CpiContext::new(  ctx.accounts.example_program.to_account_info(),  Increment {  counter: ctx.accounts.counter.to_account_info(),  },  );    // 调用增量指令  cpi::increment(cpi_ctx)?;  Ok(())  } }   #[derive(Accounts)] pub struct InitializeCpi<'info> {  #[account(mut)]  pub payer: Signer<'info>,  #[account(mut)]  pub counter: Signer<'info>,  pub system_program: Program<'info, System>,  pub example_program: Program<'info, Example>, }   #[derive(Accounts)] pub struct IncrementCpi<'info> {  #[account(mut)]  pub counter: Account<'info, Counter>,  pub example_program: Program<'info, Example>, }

说明

declare_program!() 宏接受一个参数 - 程序的 IDL 文件名(例如 example.json):

declare_program!(example); // 寻找 /idls/example.json

将生成的模块引入作用域:

use example::{  accounts::Counter, // 账户类型  cpi::{ // 跨程序调用助手  self,  accounts::{Increment, Initialize},  },  program::Example, // 程序类型 };

在账户验证结构中使用导入的类型:

#[derive(Accounts)] pub struct IncrementCpi<'info> {  // 从账户模块获取计数器类型  #[account(mut)]    pub counter: Account<'info, Counter>,    // 从程序模块获取示例类型    pub example_program: Program<'info, Example>, }

使用 CPI 模块来调用程序的指令:

pub fn initialize_cpi(ctx: Context<InitializeCpi>) -> Result<()> {  // 为初始化创建 CPI 上下文  let cpi_ctx = CpiContext::new(  ctx.accounts.example_program.to_account_info(),  Initialize {  payer: ctx.accounts.payer.to_account_info(),  counter: ctx.accounts.counter.to_account_info(),  system_program: ctx.accounts.system_program.to_account_info(),  },  );    // 调用初始化指令   cpi::initialize(cpi_ctx)?;  Ok(()) }
pub fn increment_cpi(ctx: Context<IncrementCpi>) -> Result<()> {  // 为增量创建 CPI 上下文  let cpi_ctx = CpiContext::new(  ctx.accounts.example_program.to_account_info(),  Increment {  counter: ctx.accounts.counter.to_account_info(),  },  );    // 调用增量指令   cpi::increment(cpi_ctx)?;  Ok(()) }

链下客户端

要使用 declare_program!() 宏,你需要目标程序的 IDL 文件。IDL 文件必须放置在项目中的一个名为 /idls 的目录中。/idls 目录可以在项目结构中的任何级别。例如,你的项目可以有以下布局:

example.json
main.rs
Cargo.toml

以下是目标程序的源代码 (lib.rs),它生成上面显示的 example.json IDL 文件。该程序的 IDL 接下来可以与 declare_program!() 宏在客户端脚本中一起使用,以生成客户端模块来构建程序的指令。

use anchor_lang::prelude::*;   declare_id!("6khKp4BeJpCjBY1Eh39ybiqbfRnrn2UzWeUARjQLXYRC");   #[program] pub mod example {  use super::*;    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {  let counter = &ctx.accounts.counter;  msg!("创建了计数器账户!当前计数:{}", counter.count);  Ok(())  }    pub fn increment(ctx: Context<Increment>) -> Result<()> {  let counter = &mut ctx.accounts.counter;  msg!("之前的计数器:{}", counter.count);    counter.count += 1;  msg!("计数器已增加!当前计数:{}", counter.count);  Ok(())  } }   #[derive(Accounts)] pub struct Initialize<'info> {  #[account(mut)]  pub payer: Signer<'info>,    #[account(  init,  payer = payer,  space = 8 + 8  )]  pub counter: Account<'info, Counter>,  pub system_program: Program<'info, System>, }   #[derive(Accounts)] pub struct Increment<'info> {  #[account(mut)]  pub counter: Account<'info, Counter>, }   #[account] pub struct Counter {  pub count: u64, }

以下是使用 declare_program!() 宏生成客户端模块以构建程序指令的客户端脚本(main.rs)。

use anchor_client::{  solana_client::rpc_client::RpcClient,  solana_sdk::{  commitment_config::CommitmentConfig, native_token::LAMPORTS_PER_SOL, signature::Keypair,  signer::Signer, system_program,  },  Client, Cluster, }; use anchor_lang::prelude::*; use std::rc::Rc;   declare_program!(example); use example::{accounts::Counter, client::accounts, client::args};   #[tokio::main] async fn main() -> anyhow::Result<()> {  let connection = RpcClient::new_with_commitment(  "http://127.0.0.1:8899", // 本地验证器 URL  CommitmentConfig::confirmed(),  );    // 生成密钥对并请求空投  let payer = Keypair::new();  let counter = Keypair::new();  println!("生成的密钥对:");  println!(" 付款人: {}", payer.pubkey());  println!(" 计数器: {}", counter.pubkey());    println!("\n请求 1 SOL 空投给付款人");  let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;    // 等待空投确认  while !connection.confirm_transaction(&airdrop_signature)? {  std::thread::sleep(std::time::Duration::from_millis(100));  }  println!(" 空投已确认!");    // 创建程序客户端  let provider = Client::new_with_options(  Cluster::Localnet,  Rc::new(payer),  CommitmentConfig::confirmed(),  );  let program = provider.program(example::ID)?;    // 构建并发送指令  println!("\n发送包含初始化和增量指令的交易");  let initialize_ix = program  .request()  .accounts(accounts::Initialize {  counter: counter.pubkey(),  payer: program.payer(),  system_program: system_program::ID,  })  .args(args::Initialize)  .instructions()?  .remove(0);    let increment_ix = program  .request()  .accounts(accounts::Increment {  counter: counter.pubkey(),  })  .args(args::Increment)  .instructions()?  .remove(0);    let signature = program  .request()  .instruction(initialize_ix)  .instruction(increment_ix)  .signer(&counter)  .send()  .await?;  println!(" 交易已确认:{}", signature);    println!("\n获取计数器账户数据");  let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?;  println!(" 计数器值:{}", counter_account.count);  Ok(()) }

declare_program!() 宏接受一个参数 - 程序的 IDL 文件名(例如 example.json):

declare_program!(example); // 寻找 /idls/example.json

将生成的模块引入作用域:

use example::{  accounts::Counter, // 程序账户类型  client::accounts, // 程序指令所需账户  client::args, // 程序指令所需参数 };

使用客户端模块构建程序的指令:

// 构建初始化指令 let initialize_ix = program  .request()  // 初始化指令所需账户  .accounts(accounts::Initialize {  counter: counter.pubkey(),  payer: program.payer(),  system_program: system_program::ID,  })  // 初始化指令的参数(区分符)  .args(args::Initialize)  .instructions()?  .remove(0);
// 构建增量指令 let increment_ix = program  .request()  // 增量指令所需账户  .accounts(accounts::Increment {  counter: counter.pubkey(),  })  // 增量指令的参数(区分符)  .args(args::Increment)  .instructions()?  .remove(0);

将程序指令添加到交易中并发送交易:

let signature = program  .request()  .instruction(initialize_ix)  .instruction(increment_ix)  .signer(&counter)  .send()  .await?;

使用账户模块获取并反序列化程序的账户类型:

// 从账户模块获取计数器类型 let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?;

On this page

在GitHub上编辑