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上编辑