EVM vs SVM:開發者指南
作者 Uttam Singh

每條區塊鏈都運行在一個虛擬機器上,也就是負責執行程式、套用狀態變更的引擎。Ethereum 的引擎是 EVM(Ethereum Virtual Machine),大多數 Layer 2 也採用它。Solana 的引擎是 SVM(Solana Virtual Machine),設計上讓交易可以平行執行。兩者架構上的核心差異在於:EVM 將合約的程式碼與狀態存放在一起,而 SVM 則將兩者分開存放在不同的帳戶中。這個差異決定了程式如何儲存資料、交易如何執行,以及手續費如何因壅塞而變動。
本指南將從帳戶模型、執行方式、手續費、程式設計與開發工具等面向比較兩者,並附上簡短的程式碼範例,最後提供一套判斷該在哪個平台開發或移植的框架。若你偏好影片形式,我們的 SVM vs EVM builders guide 涵蓋相同的比較內容。
EVM 和 SVM 是什麼?
EVM 是一個基於堆疊(stack-based)的虛擬機器,會逐筆處理交易,並更新單一共享的狀態樹。它最大的優勢在於相容性。同一份 Solidity 合約可以原封不動地部署到 Ethereum、它的 Layer 2(Arbitrum、Base、Optimism、Unichain、World Chain、Ink),以及獨立的 EVM 鏈(BNB Chain、Avalanche、Polygon、Monad、Berachain),而周邊工具鏈也能在任何支援該 bytecode 的地方運作。如果你對 EVM 還不熟悉,可以參考我們的 Ethereum Virtual Machine explainer,裡面說明了基本概念。
SVM 的設計圍繞著平行執行。程式會被編譯成 sBPF bytecode,這是一種基於暫存器(register-based)的格式,衍生自 eBPF(與 Linux kernel 所用的 bytecode 設計相同),而每筆交易都會事先宣告它將讀取和寫入哪些帳戶。這項宣告讓 Sealevel(Solana 的平行執行 runtime)能夠在多個 CPU 核心上同時執行彼此不衝突的交易。我們的 Solana Virtual Machine explainer 深入說明了整個執行流程。
下表整理了兩者的主要差異。
兩者的帳戶模型有什麼差異?
Ethereum 有兩種帳戶類型:由私鑰控制的外部擁有帳戶(externally-owned accounts),以及由程式碼控制的合約帳戶(contract accounts)。合約帳戶本身持有自己的儲存空間,也就是一個由 32 位元組槽位組成的鍵值儲存,並與其 bytecode 存放在一起。當你在某個 ERC-20 token 上呼叫 balanceOf 時,合約會從自己儲存空間中的一個 mapping 讀取你的餘額。程式碼與狀態共用同一個位址,無法分開。
Solana 對所有內容都採用單一帳戶模型。Solana 上每一筆狀態都是一個帳戶,結構完全相同:以 lamport(Solana 的最小單位,類似 wei)計算的餘額、一個資料欄位、一個擁有者,以及一個可執行旗標(executable flag)。程式(program)就是一種帳戶,它的資料是 sBPF bytecode,且可執行旗標設為 true。程式所管理的狀態則存放在獨立的資料帳戶中。runtime 會直接強制執行擁有權限制,因為只有擁有該帳戶的程式才能修改其資料,這消除了許多 Solidity 合約自己實作的存取控制邏輯。我們的 Solana data accounts vs. program accounts guide 詳細說明了這種區分方式。
Program derived addresses(PDA)對 EVM 開發者來說通常是最陌生的概念。Solidity 合約會用 mapping 儲存每個使用者的資料,而 Solana 程式則會根據程式的 ID 與一組 seed(通常是使用者的公鑰)為每個使用者推導出專屬的帳戶位址。這個推導過程是確定性的,產生的位址不在 Ed25519 曲線上,因此不存在對應的私鑰。只有該程式能代表它簽署交易。實務上,Solidity mapping 中的每一筆項目,在 Solana 上都會變成獨立的一個帳戶。
這兩種模型對儲存空間的計價方式也不同。在 Ethereum 上,你在寫入儲存時一次性支付 gas 費用。在 Solana 上,每個帳戶都必須持有一筆與其大小成正比的可退還保證金,稱為 rent exemption。帳戶關閉時,這筆保證金會退還,這讓開發者有直接的誘因去清除不再使用的狀態。
SVM 上的平行執行是如何運作的?
EVM 會依序執行區塊內的交易。任何一筆交易都可能讀取或寫入任意儲存槽位,而這些存取行為在交易執行前是未知的,因此 EVM 無法安全地同時執行兩筆交易。
Solana 透過要求每筆交易在執行前就列出它將讀取和寫入的所有帳戶,來消除這種不確定性。排程器會讓只讀取共享帳戶的交易平行執行,並將寫入同一帳戶的交易序列化處理。一個寫入鎖同一時間只允許一個執行緒持有。需要同一把鎖的交易會被排入佇列並依序處理,而不會因為衝突而失敗。
這個模型影響了程式設計的方式。如果某個狀態會被許多使用者同時更新,就應該拆分到多個帳戶中,SPL token(Solana 的 token 標準)正是這樣設計的。兩筆發生在不相關錢包之間的 USDC 轉帳,會寫入四個不同的 token 帳戶,因此可以同時執行。在 Ethereum 上,同樣的兩筆轉帳會同時更新同一個 ERC-20 合約的儲存空間,因此必須依序執行。
平行執行的概念也正進入 EVM 生態系。Monad 運行一條具備完整 bytecode 相容性的平行執行 EVM L1,而 MegaETH 則以 Ethereum L2 的形式採用類似技術。這些鏈在執行期間偵測衝突,而不要求交易事先宣告帳戶,藉此在維持 EVM 相容性的同時提升吞吐量。
手續費如何比較?
Ethereum 採用單一的全域手續費市場。每筆交易都支付相同的 base fee,這個費用由協議每個區塊調整並銷毀,另外還可以支付一筆選擇性的小費(priority tip)給驗證者。標準的 ETH 轉帳需要 21,000 gas,而區塊的目標用量為 30 百萬 gas,上限為 60 百萬 gas。由於 base fee 適用於整條鏈,因此某個應用程式的需求會影響到所有人。一次熱門的 NFT mint 或空投領取活動,會拉高所有使用者的 base fee,包括使用不相關應用程式的人。
Solana 採用不同的手續費結構。每筆交易支付每個簽名 5,000 lamport 的 base fee,其中一半會被銷毀,另一半支付給驗證者。運算資源以 compute unit(CU)計量,每個指令預設額度為 200,000 CU,每筆交易上限為 1.4 百萬 CU。交易也可以附加一筆以 micro-lamport 為單位計價的 prioritization fee,讓自己能優先於其他交易被排程。
關鍵差異在於手續費競爭的範圍。由於每筆交易都會指名它要寫入的帳戶,priority fee 的競爭會集中在爭奪同一批帳戶的交易之間。這種行為在生態系中被稱為「local fee market(本地手續費市場)」。getRecentPrioritizationFees 這個 RPC 方法正反映了這種設計,它會依交易將鎖定的可寫帳戶來篩選手續費樣本。在一次熱門 mint 期間,寫入該 mint 帳戶的交易手續費會上升,而不相關的交易基本上不受影響。
這影響了容量規劃的方式。在 Ethereum 上,不相關應用程式的活動可能會拉高你的交易成本,而應用程式的設計無法阻止這一點。在 Solana 上,手續費壓力主要來自你自己帳戶上的爭用,因此把頻繁寫入的狀態分散到更多帳戶上,可以降低這種壓力。
程式設計上會有什麼改變?
一份 Solidity 合約是程式碼與儲存空間合一的單一單位,且設計上是不可變的。部署後若要更改邏輯,需要採用基於 delegatecall 的代理(proxy)模式,將呼叫轉送到可替換的實作合約。一個最簡單的 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 指令,用來建立並為計數器帳戶提供資金。在 handler 執行之前,Anchor 會依照 Increment 結構檢查每個帳戶,確認該帳戶具有預期的類型與擁有者。這項檢查並不會限制誰可以呼叫該指令。以目前寫法來說,任何人都可以呼叫 increment,因此實際的程式還需要加上簽署者要求或衍生位址的限制條件,來控制誰能更新這個計數器。
預設的升級行為正好相反。以升級權限(upgrade authority)部署的 Solana 程式原生支援升級,撤銷該權限後程式就變成不可變。因此 Solana 程式不需要代理合約,這也消除了 EVM 稽核經常關注的一整類程式碼問題。
Token 也遵循類似的模式。每個 ERC-20 token 都是各自獨立的合約,實作一套標準介面,因此支援大量 token 的應用程式,就得依賴大量獨立的程式碼庫。在 Solana 上,token 是透過共用的 Token Program 發行的,而 Token-2022 提供了擴充版本,支援轉帳手續費等選擇性功能。每個 token 都是一個記錄供應量與小數位數的 mint 帳戶,而每位持有者的餘額則存放在一個 token 帳戶中。整合這些程式的應用程式,無需為每個 token 各自撰寫程式碼,就能支援任何 SPL token。
事件處理是 EVM 明顯佔優勢的地方。EVM 合約會發出帶索引的事件(indexed events),eth_getLogs 與各種索引器(indexer)可以原生消費這些事件。Solana 沒有原生的事件系統。程式只能寫入日誌訊息,而 Anchor 的事件巨集會將結構化資料編碼進這些日誌中,但日誌內容可能會被截斷。因此,Solana 上正式環境的索引通常依賴串流基礎設施,例如 webhook 和 Solana gRPC streaming。
客戶端技術堆疊長什麼樣子?
虛擬機器的選擇也決定了語言與開發工具。EVM 開發使用 Solidity,並搭配成熟的工具鏈(Foundry、Hardhat),在測試、fuzzing 與部署方面都有完善的支援。Solana 開發使用 Rust 與 Anchor,學習曲線較陡,稽核人才庫也較小,不過正在成長中。我們的 web3 programming languages overview 更詳細地介紹了各種語言選項。
資料模型的差異在客戶端程式碼中也很明顯。在 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 餘額存放在 token 帳戶中,而不是存放在 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 客戶端則需要知道每一筆資料存放在哪個帳戶中,這使得帳戶配置(account layout)成為應用程式設計的一部分。
將 EVM 應用程式移植到 Solana 會遇到哪些變動?
將應用程式從 EVM 移植到 Solana,需要重寫鏈上程式碼,因為帳戶模型與執行模型不同。主要的變動如下:
msg.sender變成簽署者帳戶(signer accounts)。 Solana 上的授權來自在交易中標記為簽署者、並由你的程式檢查的帳戶,加上當程式本身持有權限時,由程式代為簽署的 PDA。- Mapping 變成 PDA。 Solidity mapping 中的每一個 key,在使用前都必須先建立並支付 rent 保證金,成為一個獨立的衍生帳戶。讀取資料的方式也隨之改變,因為客戶端是取得帳戶,而不是查詢合約儲存空間。
- 合約儲存空間變成有固定大小、需支付 rent 的帳戶。 狀態必須事先以已知大小配置好,而帳戶關閉時保證金會退還。
- 不再需要代理升級模式。 程式的升級權限取代了代理架構,撤銷該權限後程式就變成不可變。
- 事件變成日誌與串流。 任何消費
eth_getLogs的系統,都必須改為圍繞日誌解析、webhook 或 gRPC 串流重建。 - 客戶端與測試技術堆疊會改變。 客戶端程式碼從 viem 換成
@solana/kit,測試從 Foundry 換成 Anchor 的測試框架,而 CI 也需要一個本地驗證節點(local validator)。
反方向移植的團隊,也就是從 SVM 移植到 EVM,通常會換得更完善的工具鏈、原生的事件索引,以及更大的稽核人才市場,但會失去平行執行、亞秒級確認速度,以及以帳戶為單位的手續費隔離。
無論哪個方向,團隊的學習曲線通常都是最大的成本。對於要轉往 Solana 的 EVM 工程師來說,我們的 Solana development roadmap 是個不錯的起點。
該如何在 EVM 和 SVM 之間做選擇?
Alchemy 如何支援 EVM 與 SVM 開發?
我們透過單一平台同時支援這兩個生態系。我們的 RPC endpoints 涵蓋 Ethereum、主要的 L2,以及更廣泛的 EVM 生態系。我們的 Solana infrastructure 提供快上 20 倍的 archive 呼叫速度,並具備 99.99% 的正常運行時間,而 gRPC streaming 則提供 Solana 索引所仰賴的即時資料饋送。如果你正在評估 Solana 基礎設施,我們的 Solana RPC guide 說明了選擇供應商時該注意的重點。
一組免費的 API 金鑰,就能從單一儀表板取得 EVM 各鏈與 Solana 的端點,不需要簽約或最低用量承諾,也不必為每個生態系分別整合不同的供應商。
相關總覽

打造區塊鏈魔法
Alchemy 結合最強大的 Web3 開發者產品與工具,並提供資源、社群與卓越的支援。


