跳至内容
0%

EVM vs SVM:开发者指南

发布于 2026年9月25日3 分钟阅读

EVM vs SVM:开发者指南

每条区块链都运行在一个虚拟机上,这个引擎负责执行程序并应用其状态变化。Ethereum 的引擎是 EVM(Ethereum Virtual Machine),它也为大多数 Layer 2 提供支持。Solana 的引擎是 SVM(Solana Virtual Machine),其设计目标是并行执行交易。两者的核心架构差异在于:EVM 将合约的代码和状态存储在一起,而 SVM 将它们分别保存在不同的账户中。这一差异决定了程序存储数据的方式、交易执行的方式,以及手续费应对拥堵的方式。

本指南从账户模型、执行方式、手续费、程序设计和工具链等方面对两者进行比较,并附带简短的代码示例,最后给出一个判断该在哪个平台上构建或迁移的框架。如果你更喜欢视频形式,我们的SVM vs EVM 开发者指南涵盖了相同的对比内容。

EVM 和 SVM 是什么?

EVM 是一个基于栈的虚拟机,按顺序逐一处理交易,并更新一个共享的状态树。它的主要优势是兼容性。同一份 Solidity 合约可以不加修改地部署到 Ethereum、其 Layer 2(Arbitrum、Base、Optimism、Unichain、World Chain、Ink),以及独立的 EVM 链(BNB Chain、Avalanche、Polygon、Monad、Berachain)上,周边工具也能在任何运行该字节码的地方使用。如果你对 EVM 还不熟悉,可以参考我们的Ethereum Virtual Machine 说明文档,其中介绍了基础知识。

SVM 的设计围绕并行执行展开。程序编译为sBPF 字节码,这是一种基于寄存器的格式,源自 eBPF(与 Linux 内核使用的字节码设计相同),每个交易都需要提前声明它将读取和写入哪些账户。这种声明使得 Sealevel(Solana 的并行运行时)能够在多个 CPU 核心上同时运行互不冲突的交易。我们的Solana Virtual Machine 说明文档深入介绍了其执行流程。

下表总结了两者的主要差异。

Dimension
EVM
SVM

账户模型

代码和存储捆绑在合约账户中

一切都是账户;程序本身不保存状态

执行方式

区块内按顺序执行

互不冲突的交易并行执行

虚拟机

基于栈,256 位字长

基于寄存器,源自 eBPF

手续费市场

单一的全局基础费加小费

按签名收取基础费,加上针对争用账户的优先费

主要编程语言

Solidity

Rust 配合 Anchor

代币

每个代币对应一个合约

共享的 Token Program,每个代币对应一个 mint 账户

升级

默认不可变,通过代理实现升级

默认可升级,撤销升级权限即可冻结

账户模型有何差异?

Ethereum 有两种账户类型:由私钥控制的外部账户,以及由代码控制的合约账户。合约账户持有自己的存储——一个由 32 字节槽位组成的键值存储——以及它的字节码。当你在某个 ERC-20 代币上调用 balanceOf 时,该合约会从自己存储中的映射里读取你的余额。代码和状态共享同一个地址,无法分离。

Solana 对所有内容使用单一账户模型。Solana 上的每一份状态都是一个账户,具有相同的结构:以 lamport 计的余额(Solana 的最小单位,类似 wei)、一个数据字段、一个所有者,以及一个可执行标志。程序就是一种数据为 sBPF 字节码、可执行标志为 true 的账户。程序所管理的状态存放在单独的数据账户中。运行时直接强制执行所有权规则,因为只有拥有某账户的程序才能修改其数据,这就免去了 Solidity 合约自身需要实现的大量访问控制逻辑。我们的Solana 数据账户与程序账户对比指南详细介绍了这种划分方式。

对 EVM 开发者来说,程序派生地址(PDA)通常是最陌生的概念。Solidity 合约会在映射中存储每个用户的数据,而 Solana 程序会根据该程序的 ID 和一组种子(通常是用户的公钥)为每个用户派生出专属的账户地址。这种派生是确定性的,生成的地址位于 Ed25519 曲线之外,因此不存在对应的私钥。只有该程序才能代表它签名。实际上,Solidity 映射中的每一条记录在 Solana 上都变成了独立的账户。

这两种模型对存储的计费方式也不同。在 Ethereum 上,你只需在写入时一次性支付以 gas 计的存储费用。在 Solana 上,每个账户都必须持有一笔与其大小成比例的可退还押金,称为免租金(rent exemption)。当账户被关闭时,该押金会被退还,这就给了开发者清理未使用状态的直接动力。

SVM 上的并行执行如何运作?

EVM 按顺序执行一个区块中的交易。一笔交易可以读取或写入任意存储槽,而这些访问在执行前是未知的,因此 EVM 无法安全地同时执行两笔交易。

Solana 通过要求每笔交易在执行前列出它将读取和写入的所有账户,消除了这种不确定性。调度器会并行运行那些只读取共享账户的交易,并将写入同一账户的交易串行化处理。写锁在同一时间只允许一个线程持有。需要同一把锁的交易会被排队并按顺序处理,且不会因冲突而失败。

这种模型会影响程序设计。许多用户同时更新的状态应当拆分到多个账户中,SPL 代币(Solana 的代币标准)正是这样设计的。两笔互不相关钱包之间的 USDC 转账会写入四个不同的代币账户,因此它们可以同时执行。而在 Ethereum 上,同样的两笔转账都会更新同一个 ERC-20 合约的存储,因此只能先后执行。

并行执行也正在进入 EVM 生态。Monad 运行着一条具备完整字节码兼容性的并行执行 EVM L1,MegaETH 则作为 Ethereum L2 应用了类似的技术。这些链在运行时检测冲突,而不要求交易提前声明账户,从而在保持 EVM 兼容性的同时提升吞吐量。

手续费如何对比?

Ethereum 使用单一的全局手续费市场。每笔交易支付相同的基础费,该费用由协议逐块调整并销毁,另外还可选择向验证者支付优先费小费。一笔标准的 ETH 转账消耗 21,000 gas,区块目标为 3000 万 gas,上限为 6000 万 gas。由于基础费适用于整条链,某个应用的需求会影响到所有人。一次热门的 NFT mint 或空投领取会抬高所有用户的基础费,即便是与之无关的应用的用户也不例外。

Solana 使用不同的手续费结构。每笔交易需支付每个签名 5,000 lamport 的基础费,其中一半被销毁,另一半支付给验证者。计算量以计算单元(CU)计量,每条指令默认预算为 200,000 CU,每笔交易上限为 140 万 CU。交易还可以附加一笔优先费,以每计算单元的微 lamport 计价,以便被优先调度。

两者关键的区别在于手续费竞争的范围。由于每笔交易都指明了它要写入的账户,优先费的竞争仅集中在争用同一账户的交易之间。这种行为在生态中被称为局部手续费市场。getRecentPrioritizationFees 这个 RPC 方法正体现了这一设计,它根据交易将锁定的可写账户来过滤手续费样本。在一次热门 mint 期间,写入该 mint 相关账户的交易手续费会上涨,而与之无关的交易基本不受影响。

这会影响容量规划。在 Ethereum 上,无关应用产生的活动可能会推高你的交易成本,而应用设计本身无法阻止这一点。在 Solana 上,手续费压力主要来自你自己账户上的争用,因此将频繁写入的状态分散到更多账户中可以降低这种压力。

程序设计会发生什么变化?

一份 Solidity 合约是代码和存储的单一整体,且按设计不可变。部署后要更改其逻辑,需要基于 delegatecall 构建代理模式,将调用路由到一个可替换的实现合约。一个最简的 Solidity 计数器示例如下:

solidity
Copied
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; contract Counter { uint64 public count; // state lives inside the contract itself function increment() external { count += 1; } }

与之对应的 Solana 程序使用Anchor(一个广泛使用的 Solana 程序框架)编写,程序本身不存储任何状态。计数器的值存放在一个单独的账户中,每次调用都需要显式传入该账户:

rust
Copied
use anchor_lang::prelude::*; // Run anchor keys sync to set your program ID here and in Anchor.toml. declare_id!("REPLACE_WITH_YOUR_PROGRAM_ID"); #[program] mod counter { use super::*; pub fn increment(ctx: Context<Increment>) -> Result<()> { ctx.accounts.counter.count += 1; Ok(()) } } #[derive(Accounts)] pub struct Increment<'info> { #[account(mut)] pub counter: Account<'info, Counter>, // state account passed in explicitly } #[account] pub struct Counter { pub count: u64, }

一个完整的版本还需要包含一个初始化指令,用于创建并为计数器账户注资。在处理函数运行之前,Anchor 会根据 Increment 结构体检查每个账户,确认该账户具有预期的类型和所有者。这项检查并不限制谁可以调用该指令。按目前的写法,任何人都可以调用 increment,因此一个实际使用的程序需要加入签名者要求或派生地址约束,以控制谁能更新计数器。

默认的升级行为正好相反。通过升级权限部署的 Solana 程序原生支持升级,而撤销该权限会使程序变为不可变。因此 Solana 程序不需要代理合约,这也就去除了 EVM 审计中经常重点关注的一类代码。

代币也遵循同样的模式。每个 ERC-20 代币都是独立的合约,实现一套标准接口,因此支持多种代币的应用要依赖多个独立的代码库。在 Solana 上,代币通过共享的 Token Program发行,Token-2022 则提供了一个扩展版本,支持转账手续费等可选功能。每个代币对应一个记录供应量和小数位数的 mint 账户,每位持有者的余额则存储在一个代币账户中。集成了这些程序的应用无需为每种代币编写单独的代码,就能支持任意 SPL 代币。

事件处理是 EVM 明显占优势的一个方面。EVM 合约发出的索引事件可以被 eth_getLogs 和索引器原生消费。Solana 没有原生的事件系统。程序只会写入日志消息,Anchor 的事件宏会将结构化数据编码进这些日志,而日志可能会被截断。因此,Solana 上的生产级索引通常依赖 webhook 和 Solana gRPC streaming 等流式基础设施。

客户端技术栈是什么样的?

虚拟机的选择也决定了语言和工具链。EVM 开发使用 Solidity 及成熟的工具链(Foundry、Hardhat),在测试、fuzzing 和部署方面都有广泛支持。Solana 开发使用 Rust 和 Anchor,学习曲线更陡峭,审计人员的数量也更少,不过正在增长。我们的web3 编程语言概览详细介绍了各种语言选项。

数据模型上的差异在客户端代码中也能体现出来。在 EVM 上,读取状态意味着调用合约上的某个函数,该函数从合约自身的存储中返回数据。以下示例使用 viem:

typescript
Copied
import { createPublicClient, http, parseAbi, formatUnits } from "viem"; import { mainnet } from "viem/chains"; const client = createPublicClient({ chain: mainnet, transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"), }); const balance = await client.readContract({ address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC contract abi: parseAbi(["function balanceOf(address) view returns (uint256)"]), functionName: "balanceOf", args: ["0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503"], }); console.log(formatUnits(balance, 6));

在 Solana 上,读取状态意味着获取保存该数据的账户。代币余额存储在代币账户中,而不是 Token Program 本身,因此客户端会直接读取该账户。以下示例使用 @solana/kit:

typescript
Copied
import { createSolanaRpc, address } from "@solana/kit"; const rpc = createSolanaRpc("https://solana-mainnet.g.alchemy.com/v2/YOUR_API_KEY"); // SOL balance: fetch the account's lamports by its address const { value: lamports } = await rpc .getBalance(address("83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri")) .send(); // SPL balance: read the token account directly const { value: tokenBalance } = await rpc .getTokenAccountBalance(address("2ocS3orPq3jyszjsJ4NozKWyhdotr3csDjAizmkj65aH")) .send(); console.log(lamports, tokenBalance.uiAmountString);

这种模式贯穿整个客户端层。EVM 客户端查询的是合约,而 Solana 客户端需要知道每份数据保存在哪个账户中,这使得账户布局成为应用设计的一部分。

将 EVM 应用迁移到 Solana 会有哪些变动?

将应用从 EVM 迁移到 Solana 需要重写链上代码,因为二者的账户模型和执行模型不同。主要变动包括:

  • msg.sender 变为签名者账户。 Solana 上的授权来自交易中被标记为签名者、并由你的程序检查的账户,此外还有程序可为其签名的 PDA,用于程序自身持有权限的场景。
  • 映射变为 PDA。 Solidity 映射中的每个键在 Solana 上都变成了一个派生账户,必须在首次使用前创建并支付免租金押金。读取数据的方式也随之改变,因为客户端要获取账户而不是查询合约存储。
  • 合约存储变为按大小分配、支付免租金押金的账户。 状态必须提前按已知大小分配,账户关闭时押金会被退还。
  • 不再需要代理升级模式。 程序的升级权限取代了代理机制,撤销该权限即可使程序变为不可变。
  • 事件变为日志和流式数据。 任何依赖 eth_getLogs 的系统都需要围绕日志解析、webhook 或 gRPC 流重新构建。
  • 客户端和测试技术栈发生变化。 客户端代码从 viem 迁移到 @solana/kit,测试从 Foundry 迁移到 Anchor 的测试框架,CI 需要本地验证器。

反方向迁移的团队,即从 SVM 转向 EVM,通常能获得更成熟的工具链、原生的事件索引能力和更大的审计人员市场,但会失去并行执行、亚秒级确认速度以及按账户隔离的手续费机制。

无论朝哪个方向迁移,团队的学习曲线通常是最大的成本。对于转向 Solana 的 EVM 工程师,我们的Solana 开发路线图是一个不错的起点。

如何在 EVM 和 SVM 之间做选择?

You are building
Suggested starting point
Why

高吞吐的消费级应用(支付、交易、mint)

SVM

并行执行和按账户隔离的手续费机制能让成本在你自身负载下保持稳定

与现有流动性组合的 DeFi 协议

EVM

成熟的 DeFi 协议、集成方和审计人员集中在 EVM 生态中

擅长 Solidity 且对延迟不敏感的团队

EVM,或并行 EVM 链

当工作负载不需要 SVM 的吞吐量时,留在现有技术栈可以避免重写

Alchemy 如何支持 EVM 和 SVM 开发?

我们从单一平台同时支持这两个生态。我们的RPC 端点覆盖 Ethereum、主要的 L2,以及更广泛的 EVM 生态。我们的Solana 基础设施将 archive 调用速度提升最高 20 倍,正常运行时间达到 99.99%,而gRPC streaming提供了 Solana 索引所依赖的实时数据流。如果你正在评估 Solana 基础设施,我们的Solana RPC 指南介绍了选择提供商时应关注的要点。

一个免费的 API key 即可让你在一个控制台中获得覆盖 EVM 链和 Solana 的端点,无需签订合同或满足最低用量要求,也不需要为每个生态单独集成不同的提供商。

Background gradient

构建区块链应用

Alchemy 将最强大的 Web3 开发者产品和工具与资源、社区及专业支持结合在一起。