Solidity开发者学习Solana的Anchor框架开发,需要注意的语法
Solidity开发者在学习Solana的Anchor框架开发时,需要注意以下语法和概念上的差异。Anchor框架是Rust语言的Solana智能合约开发框架,旨在简化和标准化Solana程序(Program)的开发。它与Solidity在开发风格和范式上有较大不同,因此理解这些差异对于熟悉Solidity的开发者来说至关重要。
1. 账户模型 vs EVM模型
Solidity(EVM):
- 基于以太坊虚拟机(EVM)的账户模型,每个账户都有自己的余额和存储空间。
- 合约状态变量直接存储在合约的存储空间中,调用合约时可以直接访问这些状态变量。
Solana(Anchor):
- 基于账户(Account)模型,每个账户都可以存储数据,合约逻辑(Program)不能直接存储状态。
- 程序(Program)中的状态数据通常存储在单独的PDA(Program Derived Account)或其他账户中,调用程序时需要将这些账户作为参数传递。
注意:
- 在Solana中,所有数据的读写都依赖于账户,程序本身是无状态的。
- 在调用时,需要显式地将相关账户传递给程序。
示例:
// Anchor 中的账户声明
#[account]
pub struct MyAccount {
pub data: u64, // 账户存储的数据
}
#[derive(Accounts)]
pub struct MyInstruction<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
}
2. 函数修饰符和访问控制
Solidity:
- Solidity使用修饰符(如
onlyOwner
、modifier
)来控制函数的访问权限。 - 可以通过自定义修饰符来验证调用者身份和条件。
Anchor:
- Anchor使用
#[derive(Accounts)]
和特定的属性标签(Attribute)来对函数调用进行访问控制和验证。 - 访问控制通常通过在账户结构体(如
Context
、Accounts
)中定义验证逻辑来实现。
注意:
- 在Anchor中,访问控制逻辑通常通过自定义的验证函数和检查账户条件实现,而不是像Solidity那样通过修饰符直接定义在函数上。
示例:
// Anchor 中的访问控制
#[program]
pub mod my_program {
use super::*;
// 定义一个公开的处理函数
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
let my_account = &mut ctx.accounts.my_account;
my_account.data = 42; // 初始化数据
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
3. 数据结构和序列化
Solidity:
- Solidity中数据结构(如结构体和数组)可以直接在合约中定义和使用,EVM会自动管理其内存布局和存储。
- Solidity不需要显式地处理序列化和反序列化。
Anchor(Rust):
- Anchor使用Rust的结构体定义数据结构,通常需要实现
BorshSerialize
和BorshDeserialize
trait来进行数据的序列化和反序列化。 - 数据存储在账户中,访问时需要先从账户数据中反序列化为结构体,再进行操作。
注意:
- 在Anchor中,数据存储和读取时都需要显式地进行序列化和反序列化,这与Solidity中的状态变量读写方式不同。
示例:
use anchor_lang::prelude::*;
use anchor_lang::solana_program::entrypoint::ProgramResult;
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct MyData {
pub value: u64,
}
#[account]
pub struct MyAccount {
pub data: MyData,
}
4. 交易和上下文(Transaction and Context)
Solidity:
- 在Solidity中,每个交易(transaction)对应一个状态更改操作,可以调用多个合约函数,所有上下文信息(如
msg.sender
)都内置在函数中。
Anchor:
- Anchor中的每个程序调用通常对应一个特定的指令(instruction),所有需要的账户和数据都必须作为上下文(Context)传递给指令。
- 函数的上下文通过
Context
和Accounts
结构体来管理,调用时需要显式传递所有需要的账户。
注意:
- Anchor中的上下文传递更加明确,需要手动传递所有涉及的账户和数据,而Solidity中这些信息是隐式传递的。
示例:
#[program]
pub mod my_program {
use super::*;
pub fn process(ctx: Context<MyInstruction>, new_value: u64) -> ProgramResult {
let my_account = &mut ctx.accounts.my_account;
my_account.data = new_value; // 修改账户中的数据
Ok(())
}
}
#[derive(Accounts)]
pub struct MyInstruction<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>, // 需要修改的账户
pub user: Signer<'info>, // 调用者(签名者)
}
5. 错误处理(Error Handling)
Solidity:
- Solidity使用
require
、assert
和revert
进行错误处理。 - 可以定义自定义错误码或消息,用于表示不同的错误情况。
Anchor:
- Anchor使用Rust的
Result
类型进行错误处理,错误信息通过ProgramError
或自定义的Error
类型来返回。 - Anchor中可以定义错误枚举来表示不同的错误,并与
Result
类型结合使用。
注意:
- Anchor的错误处理更加灵活和安全,但语法比Solidity更为复杂。
示例:
use anchor_lang::prelude::*;
use anchor_lang::solana_program::program_error::ProgramError;
#[error]
pub enum MyError {
#[msg("The provided value is too small.")]
ValueTooSmall,
}
#[program]
pub mod my_program {
use super::*;
pub fn process(ctx: Context<MyInstruction>, new_value: u64) -> ProgramResult {
if new_value < 10 {
return Err(MyError::ValueTooSmall.into());
}
let my_account = &mut ctx.accounts.my_account;
my_account.data = new_value;
Ok(())
}
}
6. 合约升级(Contract Upgrades)
Solidity:
- Solidity的合约升级可以通过代理模式(Proxy)来实现,即通过一个代理合约指向实际合约的地址。
- 升级时,只需要更改代理合约指向的逻辑合约地址。
Anchor:
- Anchor通过管理程序状态的账户(state account)和数据迁移来实现合约升级。
- 合约升级时,需要使用相应的工具(如
anchor upgrade
)并提供新的程序版本和数据结构的兼容性。
注意:
- Anchor的合约升级需要考虑数据结构的兼容性,必须确保新旧数据格式兼容或设计迁移逻辑。
示例:
// 假设原始的状态结构体
#[account]
pub struct OldState {
pub data: u64,
}
// 新的状态结构体(可能增加了新字段)
#[account]
pub struct NewState {
pub data: u64,
pub new_field: String,
}
// 迁移逻辑:将旧状态转换为新状态
pub fn migrate(ctx: Context<Migrate>) -> ProgramResult {
let old_state = &mut ctx.accounts.old_state;
let new_state = &mut ctx.accounts.new_state;
// 从旧状态复制数据到新状态
new_state.data = old_state.data;
new_state.new_field = String::from("default value");
Ok(())
}
#[derive(Accounts)]
pub struct Migrate<'info> {
#[account(mut)]
pub old_state: Account<'info, OldState>,
#[account(init, payer = user, space = 8 + 64)]
pub new_state: Account<'info, NewState>,
#[account(mut)]
pub user: Signer<'info>,
}
7. 多账户操作(Multi-account Operations)
Solidity:
- Solidity中多账户操作通常涉及多合约调用和委托授权(DelegateCall)。
- 可以使用
msg.sender
和msg.value
来管理跨账户操作。
Anchor:
- Anchor中多账户操作通过在上下文中传递多个账户来实现,调用时所有相关账户都必须作为参数传递。
- 可以通过条件检查确保所有账户满足特定的约束。
注意:
- Anchor中的多账户操作比Solidity更为复杂,但提供了更严格的账户验证和管理机制
。
示例:
#[program]
pub mod my_program {
use super::*;
pub fn transfer_data(ctx: Context<TransferData>, amount: u64) -> ProgramResult {
let from_account = &mut ctx.accounts.from_account;
let to_account = &mut ctx.accounts.to_account;
if from_account.data < amount {
return Err(ProgramError::InsufficientFunds);
}
from_account.data -= amount;
to_account.data += amount;
Ok(())
}
}
#[derive(Accounts)]
pub struct TransferData<'info> {
#[account(mut)]
pub from_account: Account<'info, MyAccount>,
#[account(mut)]
pub to_account: Account<'info, MyAccount>,
pub user: Signer<'info>,
}
总结
- 账户模型:Solana使用基于账户的模型,所有数据存储在账户中,而不是合约中。
- 访问控制:Anchor中访问控制通过账户结构体和上下文管理,而不是函数修饰符。
- 数据管理:Anchor需要显式地序列化和反序列化数据,而Solidity中数据管理较为自动化。
- 多账户操作:Anchor中所有涉及的账户都必须作为参数传递,而Solidity中账户和数据访问相对灵活。
- 合约升级:Anchor需要手动管理状态迁移和兼容性,而Solidity可以通过代理模式实现。
熟悉这些差异可以帮助Solidity开发者更快地适应Anchor框架的开发模式,从而更高效地构建和管理Solana程序。
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。