EVM 与 SVM:开发者指南
作者 Uttam Singh

每条区块链都运行在一个虚拟机上,虚拟机是执行程序并应用其状态变更的引擎。Ethereum 的引擎是 EVM(Ethereum 虚拟机),它同时也支撑着 Ethereum 的大多数 Layer 2。Solana 的引擎是 SVM(Solana 虚拟机),其设计目标是并行运行交易。两者在架构上的核心差异在于:EVM 将合约的代码和状态存储在一起,而 SVM 将它们保存在不同的账户中。这一差异决定了程序如何存储数据、交易如何执行,以及费用如何随拥堵而变化。
本指南从账户模型、执行、费用、程序设计和工具链几个方面对两者进行对比,附有简短的代码示例,最后给出一个决定在哪里构建或迁移的决策框架。如果你更喜欢视频,我们的 SVM 与 EVM 构建者指南涵盖了相同的对比内容。
EVM 和 SVM 是什么?
EVM 是一个基于栈的虚拟机,它逐笔处理交易,并更新单一的共享状态树。它的主要优势是兼容性。同一份 Solidity 合约无需修改即可部署到 Ethereum、其 Layer 2(Arbitrum、Base、Optimism、Unichain、World Chain、Ink)以及独立的 EVM 链(BNB Chain、Avalanche、Polygon、Monad、Berachain)上,字节码能运行的地方,周边工具也都能使用。如果你刚接触 EVM,我们的 Ethereum 虚拟机详解介绍了相关基础知识。
SVM 是围绕并行执行设计的。程序被编译为 sBPF 字节码,这是一种派生自 eBPF(Linux 内核使用的同一种字节码设计)的基于寄存器的格式,并且每笔交易都会预先声明它将读取和写入哪些账户。这种声明使 Sealevel,即 Solana 的并行运行时,能够跨 CPU 核心同时运行互不冲突的交易。我们的 Solana 虚拟机详解深入介绍了其执行管线。
下表总结了两者的主要差异。
账户模型有何不同?
Ethereum 有两种账户类型:由私钥控制的外部拥有账户,以及由代码控制的合约账户。合约账户除了字节码之外,还持有自己的存储,即一个由 32 字节槽位组成的键值存储。当你在 ERC-20 代币上调用 balanceOf 时,合约会从其自身存储中的一个映射里读取你的余额。代码和状态共用同一个地址,无法分离。
Solana 对一切都使用单一的账户模型。Solana 上的每一份状态都是一个账户,且结构相同:以 lamports 计的余额(Solana 的最小单位,类似于 wei)、一个数据字段、一个所有者,以及一个可执行标志。程序是一个数据为 sBPF 字节码、可执行标志设为 true 的账户。程序所管理的状态存放在独立的数据账户中。运行时直接强制执行所有权,只有拥有某个账户的程序才能修改其数据,这省去了 Solidity 合约需要自行实现的大部分访问控制逻辑。我们的 Solana 数据账户与程序账户指南详细介绍了这种拆分。
对 EVM 开发者来说,程序衍生地址(PDA)通常是最陌生的概念。Solidity 合约将每个用户的数据存储在映射中,而 Solana 程序则根据程序 ID 和一组种子(通常是用户的公钥)为每个用户衍生出一个专用的账户地址。这种衍生是确定性的,生成的地址位于 Ed25519 曲线之外,因此不存在对应的私钥。只有程序能代表该地址签名。实际上,Solidity 映射中的每个条目在 Solana 上都会成为一个独立的账户。
两种模型对存储的定价方式也不同。在 Ethereum 上,你在写入存储时以 gas 一次性支付费用。在 Solana 上,每个账户都必须持有一笔与其大小成正比的可退还押金,称为租金豁免。账户关闭时押金会退还,这让开发者有直接的动力去清除不再使用的状态。
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,gas 上限为 6000 万。由于基础费用作用于整条链,一个应用的需求会影响所有人。一次热门的 NFT 铸造或空投领取会抬高所有用户的基础费用,包括那些无关应用的用户。
Solana 采用不同的费用结构。每笔交易都支付每个签名 5,000 lamports 的基础费用,其中一半被销毁,一半支付给验证者。计算量以计算单元(CU)计量,每条指令的默认预算为 200,000 CU,每笔交易最多 140 万 CU。交易还可以附带优先费(以每计算单元的 micro-lamports 计价),以便先于其他交易被调度。
关键区别在于费用竞争的范围。由于每笔交易都会指明它写入的账户,优先费竞争集中在争用相同账户的交易之间。生态系统将这种行为称为局部费用市场。getRecentPrioritizationFees RPC 方法体现了这一设计:它按交易将要锁定的可写账户来筛选费用样本。在一次热门铸造期间,写入该铸造相关账户的交易费用会上涨,而无关的交易基本不受影响。
这会影响容量规划。在 Ethereum 上,无关应用的活动可能会推高你的交易成本,而应用设计无法阻止这一点。在 Solana 上,费用压力主要来自对你自己账户的争用,因此将频繁写入的状态分散到更多账户中可以降低这种压力。
程序设计有哪些变化?
Solidity 合约是代码与存储合一的单一单元,并且在设计上是不可变的。部署后要更改其逻辑,需要使用基于 delegatecall 构建的代理模式,将调用路由到一个可替换的实现合约。一个最简的 Solidity 计数器如下:
// SPDX-License-Identifier: MIT
contract Counter {
uint64 public count; // state lives inside the contract itself
function increment() external {
count += 1;
}
}等效的 Solana 程序使用 Anchor(一个广泛使用的 Solana 程序框架)编写,本身不存储任何状态。计数器的值存放在一个独立的账户中,每次调用时都需显式传入:
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");
mod counter {
use super::*;
pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.count += 1;
Ok(())
}
}
pub struct Increment<'info> {
pub counter: Account<'info, Counter>, // state account passed in explicitly
}
pub struct Counter {
pub count: u64,
}完整版本还应包含一条 initialize 指令,用于创建计数器账户并为其注资。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 流。
客户端技术栈是什么样的?
VM 的选择也决定了语言和工具。EVM 开发使用 Solidity 和成熟的工具链(Foundry、Hardhat),对测试、模糊测试和部署有广泛支持。Solana 开发使用 Rust 和 Anchor,学习曲线更陡,审计人员群体也更小,尽管这一群体正在壮大。我们的 Web3 编程语言概览更详细地介绍了各种语言选项。
数据模型的差异在客户端代码中同样可见。在 EVM 上,读取状态意味着调用合约上的某个函数,由它从自身存储中返回数据。以下示例使用 viem:
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:
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 映射中的每个键都会成为一个衍生账户,必须在首次使用前创建并预存租金。读取数据的方式也随之改变,因为客户端获取的是账户,而不是查询合约存储。
- 合约存储变为有固定大小、预存租金的账户。 状态必须按已知大小预先分配,账户关闭时押金会退还。
- 不再需要代理升级模式。 程序的升级权限取代了代理设置,撤销该权限即可使程序不可变。
- 事件变为日志和流式传输。 任何消费
eth_getLogs的系统都必须围绕日志解析、Webhook 或 gRPC 流重新构建。 - 客户端和测试技术栈发生变化。 客户端代码从 viem 迁移到
@solana/kit,测试从 Foundry 迁移到 Anchor 的测试框架,CI 需要一个本地验证者。
反方向迁移(从 SVM 到 EVM)的团队,通常会获得更成熟的工具链、原生事件索引和更大的审计市场,同时放弃并行执行、亚秒级确认和按账户隔离的费用。
无论哪个方向,团队的学习曲线通常都是最大的成本。对于转向 Solana 的 EVM 工程师,我们的 Solana 开发路线图是一个不错的起点。
如何在 EVM 和 SVM 之间做出选择?
Alchemy 如何支持 EVM 和 SVM 开发?
我们在单一平台上同时支持这两个生态系统。我们的 RPC 端点覆盖 Ethereum、主要 L2 以及更广泛的 EVM 生态系统。我们的 Solana 基础设施提供最高快 20 倍的归档调用和 99.99% 的正常运行时间,gRPC 流则提供 Solana 索引所依赖的实时数据流。如果你正在评估 Solana 基础设施,我们的 Solana RPC 指南介绍了选择服务商时需要关注的要点。
一个免费的 API 密钥即可让你在同一个控制台中获得 EVM 链和 Solana 的端点,无需签订合同或承诺最低用量,也无需为每个生态系统分别集成不同的服务商。
相关概览

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


