多链对比: EVM vs Solana vs Move 架构对比(上) - 编程模型与账户体系
### 一、三大平台设计哲学总览
日期: 2026-06-28 方向: 多链 阶段: 第四阶段:综合实战 标签: #多链对比 #EVM #Solana #Move #账户模型 #编程范式
今日目标
经过80天对 Solidity、Anchor(Solana)、Move(Sui/Aptos) 的深入学习,今天开始进行系统性的多链架构对比。本篇聚焦底层架构差异——账户模型、执行模型、存储模型、Gas/费用机制和编程范式,从"第一性原理"角度理解三大智能合约平台的设计哲学。
对于产品经理和架构师来说,理解这些底层差异直接决定了:
- 你的产品应该部署在哪条链
- 用户体验会受到哪些约束
- 安全模型有哪些根本区别
- 开发成本和迭代速度如何
核心概念
一、三大平台设计哲学总览
| 维度 | EVM (Ethereum/L2) | Solana (SVM) | Move (Sui/Aptos) |
|---|---|---|---|
| 诞生年份 | 2015 | 2020 | 2022 |
| 设计哲学 | 通用世界计算机 | 高性能交易引擎 | 安全资产管理 |
| 核心语言 | Solidity/Vyper | Rust (Anchor) | Move |
| 虚拟机 | EVM (栈机器) | SVM (BPF/SBF) | Move VM |
| 共识 | PoS (Gasper) | PoH + Tower BFT | Narwhal & Bullshark (Sui) |
| TPS (实际) | ~30 (L1), ~2000 (L2) | ~3000-5000 | ~10000+ (Sui) |
| 最终确认 | ~12分钟 (L1) | ~400ms | ~500ms (Sui) |
| 编程范式 | OOP (面向对象) | Rust风格 (系统编程) | 资源导向 (Resource-oriented) |
二、账户模型深度对比
2.1 EVM: 全局状态树模型
EVM 采用**全局状态树(Global State Trie)**模型,所有状态存储在一棵 Merkle Patricia Trie 中。
EVM 账户结构:
┌──────────────────────────────┐
│ Account │
│ ┌────────────────────────┐ │
│ │ nonce: uint256 │ │ ← 交易计数
│ │ balance: uint256 │ │ ← ETH余额
│ │ storageRoot: bytes32 │ │ ← 存储树根哈希
│ │ codeHash: bytes32 │ │ ← 合约代码哈希
│ └────────────────────────┘ │
│ │
│ Storage (合约账户才有): │
│ ┌────────────────────────┐ │
│ │ slot[0] → value │ │
│ │ slot[1] → value │ │
│ │ slot[keccak(key)] → v │ │ ← mapping存储
│ │ ... │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
关键特征:
- 两种账户类型: EOA(外部拥有账户) 和 Contract Account(合约账户)
- 合约拥有自己的存储空间,状态数据存储在合约内部
- 所有 ERC20 代币余额存储在代币合约的 mapping 中,而非用户账户中
- 全局状态意味着状态读写可能产生冲突,难以并行执行
// EVM 中的代币存储方式 — 余额存在合约里
contract ERC20 {
// 所有用户的余额都存储在这个合约的 storage 中
mapping(address => uint256) private _balances;
// 转账: 修改合约内部的两个 storage slot
function transfer(address to, uint256 amount) external {
_balances[msg.sender] -= amount; // slot = keccak256(msg.sender, 0)
_balances[to] += amount; // slot = keccak256(to, 0)
}
}
2.2 Solana: Account Model (显式账户模型)
Solana 采用显式账户模型,程序(Program)和数据(Account)完全分离。
Solana 账户结构:
┌──────────────────────────────┐
│ Account │
│ ┌────────────────────────┐ │
│ │ lamports: u64 │ │ ← SOL余额(1 SOL = 10^9 lamports)
│ │ data: Vec<u8> │ │ ← 任意字节数据
│ │ owner: Pubkey │ │ ← 拥有此账户的程序
│ │ executable: bool │ │ ← 是否是程序
│ │ rent_epoch: u64 │ │ ← 租金周期
│ └────────────────────────┘ │
└──────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Solana 的程序-数据分离 │
│ │
│ Program Account Data Account │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ executable │ │ owner = │ │
│ │ = true │◄──────│ program_id │ │
│ │ code (BPF) │ │ data = {...} │ │
│ └─────────────┘ └──────────────┘ │
│ │
│ 一个 Program 可以拥有无数个 Data Account │
└─────────────────────────────────────────────┘
关键特征:
- 程序(Program)不存储状态——所有状态存储在独立的 Account 中
- 每个 Account 有一个
owner字段,指向拥有它的 Program - 只有 owner Program 才能修改 Account 的 data 字段
- 交易必须声明所有将要读写的 Account (有利于并行执行)
- Token 余额存储在用户自己的 Token Account 中(Associated Token Account)
// Solana/Anchor 中的代币存储方式 — 每个用户有自己的 Token Account
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub sender: Signer<'info>,
// 发送方的 Token Account (独立账户,存储余额)
#[account(
mut,
constraint = sender_token.owner == sender.key(),
)]
pub sender_token: Account<'info, TokenAccount>,
// 接收方的 Token Account (独立账户)
#[account(mut)]
pub receiver_token: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
pub fn transfer(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
// CPI: 调用 Token Program 修改两个独立 Account 的数据
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.sender_token.to_account_info(),
to: ctx.accounts.receiver_token.to_account_info(),
authority: ctx.accounts.sender.to_account_info(),
},
),
amount,
)?;
Ok(())
}
2.3 Move (Sui): Object Model (对象模型)
Sui 采用对象模型(Object Model),一切皆对象(Object),资源天然不可复制。
Sui Object 模型:
┌──────────────────────────────────────────┐
│ Object │
│ ┌────────────────────────────────────┐ │
│ │ id: UID (全局唯一) │ │
│ │ owner: 地址 / Shared / Immutable │ │
│ │ type: 结构体类型 │ │
│ │ data: 结构体字段 │ │
│ └────────────────────────────────────┘ │
│ │
│ 所有权类型: │
│ ├── Owned: 单一地址拥有 (可并行) │
│ ├── Shared: 所有人可访问 (需共识) │
│ └── Immutable: 不可变 (可并行) │
└──────────────────────────────────────────┘
关键特征:
- Move 语言的线性类型系统: 资源(Resource)不能被复制或隐式丢弃
Coin<SUI>就是一个 Object,拥有所有权和转移语义- Owned Object 之间的交易可以跳过共识,实现亚秒级确认
- 天然防止双花和重入——资源在同一时刻只能存在于一个位置
// Move (Sui) 中的代币存储方式 — 代币就是对象
module example::token_transfer {
use sui::coin::{Self, Coin};
use sui::sui::SUI;
use sui::transfer;
use sui::tx_context::TxContext;
// 转账: 将 Coin 对象的所有权从发送方转移到接收方
public entry fun send_coin(
coin: Coin<SUI>, // 发送方拥有的 Coin 对象
recipient: address,
_ctx: &mut TxContext,
) {
// 转移所有权 — 编译器保证 coin 之后不能再被使用
transfer::public_transfer(coin, recipient);
// coin 在此处已经"消失",不可能双花
}
// 拆分 + 转账
public entry fun split_and_send(
coin: &mut Coin<SUI>, // 可变引用
amount: u64,
recipient: address,
ctx: &mut TxContext,
) {
let split_coin = coin::split(coin, amount, ctx);
transfer::public_transfer(split_coin, recipient);
}
}
三、执行模型对比
| 维度 | EVM | Solana SVM | Move VM |
|---|---|---|---|
| 执行方式 | 顺序执行 | 并行执行 (Sealevel) | 并行执行 (Owned Object) |
| 并行策略 | 无 (L1),乐观并行 (部分L2) | 声明式并行——交易预声明读写Account | 基于对象所有权自动并行 |
| 调用模型 | 内部调用 (CALL/DELEGATECALL) | CPI (Cross-Program Invocation) | Module 函数调用 |
| 栈深度限制 | 1024 | ~4层CPI | 无硬性限制 |
| 计算单位 | Gas (21000基础) | Compute Units (200K默认/1.4M最大) | Gas (类似EVM但更便宜) |
并行执行的关键差异
EVM 顺序执行:
TX1 ──► TX2 ──► TX3 ──► TX4 (一个一个来)
Solana 声明式并行:
TX1 (accounts: [A, B]) ──┐
TX2 (accounts: [C, D]) ──┤──► 并行执行 (无冲突)
TX3 (accounts: [A, E]) ──┘──► TX1 和 TX3 冲突,顺序执行
Sui 基于所有权并行:
TX1 (owned obj X) ────────┐
TX2 (owned obj Y) ────────┤──► 全部并行 (owned = 无共识)
TX3 (shared obj Z) ───────┘──► shared 才需要共识
四、存储模型与成本
| 维度 | EVM | Solana | Sui |
|---|---|---|---|
| 存储位置 | 合约内部 Storage | 独立 Account | Object |
| 存储成本 | 一次性写入(20000 gas) | 租金(Rent)或免租金(2年) | 存储基金(Storage Fund) |
| 存储大小限制 | 理论无限(Gas限制) | 10MB/Account | Object大小限制 |
| 状态膨胀问题 | 严重(永久存储) | 较好(租金机制) | 较好(存储基金) |
| 删除退款 | 是(SSTORE清零退Gas) | 是(关闭Account退租金) | 是(删除Object退费) |
// EVM 存储成本示例
contract StorageCost {
uint256 public value;
// 冷 SSTORE (从0到非0): 20,000 gas ≈ $0.5-2 (取决于Gas价格)
// 热 SSTORE (非0到非0): 2,900 gas
// SSTORE 清零: 退还 4,800 gas
function setValue(uint256 _value) external {
value = _value; // 首次写入最贵
}
}
// Solana 租金计算
// rent_exempt_minimum = 账户数据大小 * 每字节费率 * 2年
// 例: 165 bytes 的 Token Account ≈ 0.00203928 SOL
// 关闭账户时,租金全额退还
#[account]
pub struct GameState {
pub player: Pubkey, // 32 bytes
pub score: u64, // 8 bytes
pub level: u8, // 1 byte
// 总计 ~41 bytes + 8 bytes (Anchor discriminator) = 49 bytes
// rent_exempt ≈ 0.00114144 SOL
}
五、Gas/费用机制对比
| 维度 | EVM | Solana | Sui |
|---|---|---|---|
| 费用单位 | Gas × Gas Price | 固定基础费 + Priority Fee | Gas Units × Gas Price |
| 典型转账费 | ~$0.5-5 (L1), ~$0.01 (L2) | ~$0.00025 | ~$0.001-0.01 |
| 复杂交易费 | $5-100+ (L1) | ~$0.001-0.01 | ~$0.01-0.1 |
| 费用可预测性 | 低 (Gas Price波动) | 高 (基础费固定) | 中 |
| 费用支付 | 只能ETH | 只能SOL | SUI (支持赞助交易) |
| 费用赞助 | ERC-4337 Paymaster | 无原生支持 | 原生支持(Sponsored TX) |
六、编程范式深度对比
6.1 同一逻辑的三种实现: 简易金库(Vault)
Solidity (OOP 面向对象):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SimpleVault {
// 状态变量 — 存储在合约内部
mapping(address => uint256) public balances;
IERC20 public immutable token;
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
constructor(address _token) {
token = IERC20(_token);
}
function deposit(uint256 amount) external {
token.transferFrom(msg.sender, address(this), amount);
balances[msg.sender] += amount;
emit Deposited(msg.sender, amount);
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
token.transfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
}
Anchor/Rust (系统编程风格):
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
declare_id!("Vault111111111111111111111111111111111111111");
#[program]
pub mod simple_vault {
use super::*;
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
// CPI: 转移代币到 vault token account
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.user_token.to_account_info(),
to: ctx.accounts.vault_token.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
},
),
amount,
)?;
// 更新用户余额记录
ctx.accounts.user_state.deposited += amount;
Ok(())
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
require!(
ctx.accounts.user_state.deposited >= amount,
VaultError::InsufficientBalance
);
// PDA 签名的 CPI
let seeds = &[b"vault", &[ctx.bumps.vault_auth]];
let signer = &[&seeds[..]];
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.vault_token.to_account_info(),
to: ctx.accounts.user_token.to_account_info(),
authority: ctx.accounts.vault_auth.to_account_info(),
},
signer,
),
amount,
)?;
ctx.accounts.user_state.deposited -= amount;
Ok(())
}
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(mut, seeds = [b"user", user.key().as_ref()], bump)]
pub user_state: Account<'info, UserState>,
#[account(mut)]
pub user_token: Account<'info, TokenAccount>,
#[account(mut)]
pub vault_token: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
#[account]
pub struct UserState {
pub deposited: u64,
}
Move/Sui (资源导向):
module vault::simple_vault {
use sui::coin::{Self, Coin};
use sui::balance::{Self, Balance};
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
// Vault 是一个 shared object
struct Vault<phantom T> has key {
id: UID,
balance: Balance<T>,
}
// 用户存款凭证 — 一个 owned object
struct Receipt<phantom T> has key, store {
id: UID,
amount: u64,
}
// 创建 Vault
public fun create_vault<T>(ctx: &mut TxContext) {
let vault = Vault<T> {
id: object::new(ctx),
balance: balance::zero(),
};
transfer::share_object(vault);
}
// 存款: Coin 被消耗(线性类型保证),返回 Receipt
public fun deposit<T>(
vault: &mut Vault<T>,
coin: Coin<T>,
ctx: &mut TxContext,
): Receipt<T> {
let amount = coin::value(&coin);
let coin_balance = coin::into_balance(coin);
// coin 在此已被消耗,不可能双花
balance::join(&mut vault.balance, coin_balance);
Receipt<T> {
id: object::new(ctx),
amount,
}
}
// 取款: Receipt 被消耗,返回 Coin
public fun withdraw<T>(
vault: &mut Vault<T>,
receipt: Receipt<T>,
ctx: &mut TxContext,
): Coin<T> {
let Receipt { id, amount } = receipt;
object::delete(id);
// receipt 被销毁,不可能重复取款
let withdrawn = balance::split(&mut vault.balance, amount);
coin::from_balance(withdrawn, ctx)
}
}
6.2 编程范式总结
| 范式特征 | Solidity (OOP) | Rust/Anchor (系统编程) | Move (资源导向) |
|---|---|---|---|
| 状态管理 | 合约内 mapping/变量 | 独立 Account + PDA | Object (Owned/Shared) |
| 所有权 | 无原生概念 | Rust 所有权 (编译期) | 线性类型 (编译期+运行时) |
| 安全保证 | 运行时检查 | 编译期(Rust)+运行时 | 编译期+类型系统 |
| 可组合性 | 高 (任意合约调用) | 中 (CPI + Account约束) | 高 (模块+泛型) |
| 学习曲线 | 低 (类似JS/Java) | 高 (Rust + Solana概念) | 中 (新语言但直觉清晰) |
| 代码量 | 最少 | 最多 (Account声明冗长) | 中等 |
关键要点总结
-
EVM 的优势在生态和可组合性: 全球最大的开发者社区、最丰富的工具链、最多的 DeFi 协议。缺点是性能和并行能力有限。
-
Solana 的优势在性能: 通过声明式并行和高效共识实现高TPS低延迟。缺点是开发体验相对复杂、Account 模型学习曲线陡。
-
Move/Sui 的优势在安全模型: 线性类型从根本上消除了双花、重入等问题。Object 模型让并行执行更自然。缺点是生态仍在早期。
-
账户模型决定了一切: EVM的全局状态→难并行但好组合;Solana的分离账户→好并行但代码冗长;Move的对象模型→安全但生态小。
-
没有"最好"的链,只有"最适合"的链: 选择取决于你的产品需求、目标用户、安全要求和开发团队能力。
常见误区
-
误区: Solana 比 Ethereum 更好因为更快 — 速度只是一个维度。对于高价值 DeFi 操作,安全性和去中心化可能更重要。
-
误区: Move 的线性类型完全消除了安全问题 — 线性类型消除了资源层面的问题,但逻辑漏洞(如错误的价格计算、不当的访问控制)仍然存在。
-
误区: EVM 顺序执行意味着不能扩展 — L2 方案(Rollups)极大提升了 EVM 生态的可扩展性,且保留了可组合性。
-
误区: 选链 = 选语言 — 实际上选链更多取决于生态(用户在哪里)、流动性(资金在哪里)、工具链成熟度。
面试关联
核心面试题: "Compare smart contract platforms — EVM vs Solana vs Move"
30秒版本:
三大平台代表三种不同的设计哲学。EVM 优先可组合性和开发者体验,用全局状态和顺序执行换取最大生态兼容性;Solana 优先性能,用声明式账户模型实现并行执行和亚秒级确认;Move/Sui 优先安全性,用线性类型系统从根本上消除资源类漏洞。选择取决于产品需求——高价值 DeFi 选 EVM 生态,高频交易选 Solana,新范式应用可以考虑 Move。
追问准备:
-
Q: 如果你要做一个 NFT 游戏,选哪条链?
- A: Sui。原因: (1) Object 模型天然适合 NFT 管理,每个 NFT 就是一个 owned object;(2) 亚秒级确认适合游戏交互;(3) 并行执行支持高并发;(4) 存储基金模式对长期存储友好。
-
Q: 为什么 EVM 仍然主导 DeFi?
- A: 网络效应。(1) 最多的流动性和用户;(2) 最成熟的基础设施(预言机、桥、钱包);(3) 最大的开发者社区;(4) 可组合性——新协议可以直接调用已有协议。
参考资源
- Ethereum Yellow Paper — EVM 规范
- Solana Documentation - Programming Model — Solana 账户模型
- Sui Move Documentation — Sui Object 模型
- Aptos Move Book — Move 语言规范
- L2Beat — L2 生态数据对比
- DeFiLlama — 多链 TVL 和活跃度数据