掘金 后端 ( ) • 2024-04-29 16:56

在 Anchor 框架中,有多种不同类型的账户和相关模块,每种类型都有其特定的用途和场景。Anchor对其进行了封装,不同的庄户类型有不同的作用,那我们在写合约的时候可能会疑惑,这么多种账户系统,我怎么知道我要写的账户应该用哪种方式表示呢?下面我们就来详细进行研究一下,Anchor提供的工具怎么开用的顺手

Anchor 一共定义了9种不同的账户类型,用来适应不同的情况,看看你了解几种?

账户类型 用途 使用场景 account 用于定义由智能合约管理的数据结构 创建存储数据的账户,如用户信息、游戏状态等 account_info 提供账户的低级访问,包括密钥、余额、所有者等信息 在合约中手动处理账户数据或需要访问账户的详细信息时使用 account_loader 用于延迟加载账户数据,通常用于大型数据账户 处理大型数据结构时使用,优化性能和成本 interface_account 用于定义可以实现特定接口的账户 当合约逻辑需要与实现了某个接口的其他合约互动时使用 program 用于定义与其他合约程序的交互 当合约需要调用其他合约的功能或方法时使用 signer 代表需要签名的账户,通常是用户的钱包账户 处理需要用户授权的交易时使用 system_account 代表系统账户,如 Solana 的系统程序 进行如创建账户、转账等系统级操作时使用 sysvar 用于访问系统变量,如当前区块时间、租金配置等 合约需要读取链上的环境或配置信息时使用 unchecked_account 用于账户,其数据不会在运行时被自动解码或验证 需要手动处理账户数据或执行非标准操作时使用

这些账户类型在 Anchor 框架中的设计使得智能合约开发更加模块化和安全,同时提供了灵活的方式来处理各种区块链上的情况。

但是看完还是有点懵,来看看是怎么在代码中使用的吧:Anchor 中每种账户类型的 Rust 代码示例

1. account

  • 用途: account 装饰器用于定义一个由智能合约管理的数据结构,这些结构映射到链上的账户,并由 Anchor 框架自动处理序列化和反序列化。
  • 场景: 当你需要在区块链上持久化存储结构化数据时,如用户资料、游戏状态或任何其他应用特定数据。

代码示例:

use anchor_lang::prelude::*;

#[account]
pub struct UserProfile {
    pub user_id: u64,
    pub username: String,
    pub email: String,
    pub score: u32,
}

#[derive(Accounts)]
pub struct CreateUserProfile<'info> {
    #[account(init, payer = user, space = 8 + 32 + 40 + 40 + 4)] // 分配足够的空间
    pub user_profile: Account<'info, UserProfile>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

pub fn create_user_profile(ctx: Context<CreateUserProfile>, username: String, email: String) -> Result<()> {
    let profile = &mut ctx.accounts.user_profile;
    profile.user_id = ctx.accounts.user.key().to_bytes().iter().fold(0, |acc, &b| acc * 256 + b as u64); // 生成一个简单的用户ID
    profile.username = username;
    profile.email = email;
    profile.score = 0;
    Ok(())
}

2. account_info

  • 用途: account_info 提供对账户的低级访问,包括账户的公钥、余额、所有者和其他元数据。它不自动处理数据的反序列化,让开发者可以手动处理账户数据。
  • 场景: 用于处理不由当前合约管理的账户,或者当你需要直接访问和操作账户的底层数据时,如进行资金转移、查询账户余额等。

代码示例:

use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct TransferFunds<'info> {
    #[account(mut)]
    pub from_account: AccountInfo<'info>,
    #[account(mut)]
    pub to_account: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

pub fn transfer_funds(ctx: Context<TransferFunds>, amount: u64) -> Result<()> {
    // 检查余额是否充足
    if **ctx.accounts.from_account.lamports.borrow() < amount {
        return Err(ProgramError::InsufficientFunds.into());
    }

    // 从一个账户转账到另一个账户
    **ctx.accounts.from_account.lamports.borrow_mut() -= amount;
    **ctx.accounts.to_account.lamports.borrow_mut() += amount;
    Ok(())
}

3. account_loader

  • 用途: account_loader 用于延迟加载账户数据,这对于处理大型数据结构特别有用,因为它允许合约在需要时才加载数据,从而可以节省资源和提高执行效率。
  • 场景: 主要用于访问和操作数据量大的账户,例如大型的游戏状态、复杂的金融产品数据或任何大型数据集。

代码示例:

use anchor_lang::prelude::*;
use anchor_lang::solana_program::entrypoint::ProgramResult;

#[account(zero_copy)]
pub struct LargeData {
    pub data: [u64; 1024],  // 示例: 大型数据数组
}

#[derive(Accounts)]
pub struct ProcessLargeData<'info> {
    #[account(zero_copy)]
    pub large_data_account: AccountLoader<'info, LargeData>,
}

pub fn process_data(ctx: Context<ProcessLargeData>, index: usize, value: u64) -> ProgramResult {
    let mut data_account = ctx.accounts.large_data_account.load_mut()?;
    data_account.data[index] = value;  // 更新特定索引处的数据
    Ok(())
}

4. interface_account

  • 用途: interface_account 用于定义一个可以实现特定接口的账户。这允许合约通过一个统一的接口与多个不同的实现互动,增加了合约的灵活性和可扩展性。
  • 场景: 当你的合约需要与实现了某个接口的不同合约进行交互时,例如不同类型的代币合约或其他遵循特定规范的服务。

代码示例:

use anchor_lang::prelude::*;
use anchor_lang::solana_program::program_pack::Pack;

#[interface]
pub trait Token {
    fn transfer(&self, to: Pubkey, amount: u64) -> ProgramResult;
}

#[derive(Accounts)]
pub struct TransferTokens<'info> {
    #[account(executable)]
    pub token_program: InterfaceAccount<'info, dyn Token>,
    pub from: Signer<'info>,
    pub to: AccountInfo<'info>,
}

pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> ProgramResult {
    ctx.accounts.token_program.transfer(ctx.accounts.to.key(), amount)
}

5. program

  • 用途: program 用于定义合约代码与其他 Solana 程序的交互。这使得合约可以调用其他程序提供的功能,如系统功能、代币操作等。
  • 场景: 当你需要在你的合约中调用其他合约或系统功能时使用,如代币转账、账户创建等。

代码示例:

use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct CallAnotherProgram<'info> {
    pub system_program: Program<'info, System>,
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(init, payer = payer, space = 8 + 8)]
    pub new_account: AccountInfo<'info>,
}

pub fn create_account(ctx: Context<CallAnotherProgram>, lamports: u64) -> Result<()> {
    let rent = Rent::get()?;
    let required_lamports = rent.minimum_balance(ctx.accounts.new_account.data_len());
    let seeds = &[b"new_account_seed"[..], &[bump_seed]];
    let bump_seed = Pubkey::find_program_address(seeds, ctx.program_id).1;

    anchor_lang::system_program::create_account(
        CpiContext::new(
            ctx.accounts.system_program.to_account_info(),
            anchor_lang::system_program::CreateAccount {
                from: ctx.accounts.payer.to_account_info(),
                to: ctx.accounts.new_account.to_account_info(),
                lamports: lamports,
                space: ctx.accounts.new_account.data_len() as u64,
                owner: ctx.program_id,
            },
        ),
    )?;
    Ok(())
}

6. signer

  • 用途: signer 代表需要参与交易签名的账户,通常是指用户的钱包账户或其他需要用户明确授权的账户。
  • 场景: 用于处理需要用户授权的交易,例如转账、修改敏感数据等。

代码示例:

use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct ExecuteTransaction<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    #[account(mut)]
    pub storage_account: Account<'info, Storage>,
}

#[account]
pub struct Storage {
    pub data: u64,
}

pub fn update_storage(ctx: Context<ExecuteTransaction>, new_data: u64) -> Result<()> {
    let account = &mut ctx.accounts.storage_account;
    account.data = new_data;
    Ok(())
}

7. ``sysvar`

  • 用途: sysvar 用于访问 Solana 网络提供的系统变量,这些系统变量提供了链上的环境信息,如当前区块时间、最近区块哈希、租金配置等。
  • 场景: 当你的合约需要根据当前的区块链状态或环境来调整其行为时,比如计算租金,获取时间戳等。

代码示例:

use anchor_lang::prelude::*;
use anchor_lang::solana_program::sysvar::clock::Clock;

#[derive(Accounts)]
pub struct CheckTime<'info> {
    // 直接访问 Clock sysvar
    pub clock: Sysvar<'info, Clock>,
}

pub fn check_current_time(ctx: Context<CheckTime>) -> Result<()> {
    let clock = &ctx.accounts.clock;
    msg!("Current slot: {}", clock.slot);
    msg!("Current timestamp: {}", clock.unix_timestamp);
    Ok(())
}

8. `system_account

  • 用途: system_account 代表 Solana 系统程序的账户,这个程序负责基础的网络操作,如账户创建、资金转账等。
  • 场景: 当你的合约需要执行如创建新账户、转账等基础的网络操作时使用。

代码示例:

use anchor_lang::prelude::*;
use anchor_lang::solana_program::system_program;

#[derive(Accounts)]
pub struct CreateNewAccount<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(init, payer = payer, space = 8 + 64)]
    pub new_account: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

pub fn create_account(ctx: Context<CreateNewAccount>, lamports: u64) -> Result<()> {
    let rent = Rent::get()?;
    let required_lamports = rent.minimum_balance(ctx.accounts.new_account.data_len());

    system_program::create_account(
        CpiContext::new_with_signer(
            ctx.accounts.system_program.to_account_info(),
            system_program::CreateAccount {
                from: ctx.accounts.payer.to_account_info(),
                to: ctx.accounts.new_account.to_account_info(),
                lamports: required_lamports,
                space: ctx.accounts.new_account.data_len() as u64,
                owner: ctx.program_id,
            },
            &[&[b"new_account_seed", &[bump_seed]]],
        ),
    )?;
    Ok(())
}

9. unchecked_account

  • 用途: unchecked_account 用于表示一个账户,其数据不会在运行时被 Anchor 自动解码或验证。这意味着 Anchor 不会自动处理这个账户的数据结构和类型安全检查。

  • 场景: 此类型通常用于以下情况:

    • 当合约开发者需要完全控制对账户的低级访问时。
    • 在处理一些特定的外部账户时,这些账户可能没有预定义的数据结构,需要开发者自行解析和验证数据。
    • 当需要优化性能,避免自动数据解析带来的开销时。

这里是一个简单的例子,展示如何在 Anchor 中使用 unchecked_account

use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct UseUncheckedAccount<'info> {
    pub user: Signer<'info>,
    // 声明一个 unchecked_account
    pub data_account: UncheckedAccount<'info>,
}

pub fn access_unchecked_account(ctx: Context<UseUncheckedAccount>) -> Result<()> {
    // 直接访问未经检查的账户的数据
    let data = ctx.accounts.data_account.try_borrow_data()?;
    
    // 开发者需要自己处理数据解码
    let custom_data = CustomData::try_from_slice(&data)?;
    msg!("Custom data: {:?}", custom_data);

    Ok(())
}

// 假设有一个自定义数据结构
#[derive(Debug)]
struct CustomData {
    pub field1: u32,
    pub field2: u64,
}

impl CustomData {
    fn try_from_slice(data: &[u8]) -> Result<Self, ProgramError> {
        let field1 = u32::from_le_bytes(data[0..4].try_into().unwrap());
        let field2 = u64::from_le_bytes(data[4..12].try_into().unwrap());
        Ok(Self { field1, field2 })
    }
}

在这个示例中,我们看到 data_account 被声明为 UncheckedAccount,这允许开发者直接访问和操作原始数据。此时,数据的正确性和解码逻辑完全由开发者控制,这提供了灵活性但同时增加了错误的可能性。Pomelo_刘金。转载请注明原文链接。感谢!