在 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 框架自动处理序列化和反序列化。 - 场景: 当你需要在区块链上持久化存储结构化数据时,如用户资料、游戏状态或任何其他应用特定数据。
代码示例:
rust
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
提供对账户的低级访问,包括账户的公钥、余额、所有者和其他元数据。它不自动处理数据的反序列化,让开发者可以手动处理账户数据。 - 场景: 用于处理不由当前合约管理的账户,或者当你需要直接访问和操作账户的底层数据时,如进行资金转移、查询账户余额等。
代码示例:
rust
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
用于延迟加载账户数据,这对于处理大型数据结构特别有用,因为它允许合约在需要时才加载数据,从而可以节省资源和提高执行效率。 - 场景: 主要用于访问和操作数据量大的账户,例如大型的游戏状态、复杂的金融产品数据或任何大型数据集。
代码示例:
rust
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
用于定义一个可以实现特定接口的账户。这允许合约通过一个统一的接口与多个不同的实现互动,增加了合约的灵活性和可扩展性。 - 场景: 当你的合约需要与实现了某个接口的不同合约进行交互时,例如不同类型的代币合约或其他遵循特定规范的服务。
代码示例:
rust
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 程序的交互。这使得合约可以调用其他程序提供的功能,如系统功能、代币操作等。 - 场景: 当你需要在你的合约中调用其他合约或系统功能时使用,如代币转账、账户创建等。
代码示例:
rust
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
代表需要参与交易签名的账户,通常是指用户的钱包账户或其他需要用户明确授权的账户。 - 场景: 用于处理需要用户授权的交易,例如转账、修改敏感数据等。
代码示例:
rust
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 网络提供的系统变量,这些系统变量提供了链上的环境信息,如当前区块时间、最近区块哈希、租金配置等。 - 场景: 当你的合约需要根据当前的区块链状态或环境来调整其行为时,比如计算租金,获取时间戳等。
代码示例:
rust
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 系统程序的账户,这个程序负责基础的网络操作,如账户创建、资金转账等。 - 场景: 当你的合约需要执行如创建新账户、转账等基础的网络操作时使用。
代码示例:
rust
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
:
rust
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_刘金。转载请注明原文链接。感谢!