EVM Gas 計算完整指南:數學推導、實務計算與最佳化策略
本文從頭到尾把 Gas 計算的數學邏輯推導一遍,配上 Solidity、JavaScript、Python 三種語言的程式碼範例,讓你知道合約的 Gas 消耗是怎麼跑出來的。涵蓋 EIP-1559 費用模型、Opcode 執行成本、SSTORE 機制、跨合約呼叫、退款模型、以及實用的 Gas 最佳化技巧。
EVM Gas 計算完整指南:數學推導、實務計算與最佳化策略
我第一次認真算 Gas 的時候,是因為用戶抱怨我的合約交易費用太高。那時候我還天真地以為「不就是 21,000 Gas 基礎費加上點計算嗎」。後來才發現,我合約裡那個看起來無害的 for 迴圈,居然吃掉了好幾萬 Gas。
這篇文章把 Gas 計算的數學邏輯從頭到尾推導一遍,再配上程式碼讓你知道「在 Solidity 裡,這個數字是怎麼跑出來的」。不管你是要面試、備考以太坊開發認證,還是想寫出更省 Gas 的合約,這篇都能派上用場。
資料截止 2026 年 3 月,所有計算基於 EIP-1559 之後的修正版。
基礎概念:什麼是 Gas?
在以太坊網路中,Gas 是衡量「工作量的單位」。你可以把它想成汽車的汽油——執行智能合約需要消耗 Gas,而 Gas 需要用 ETH 來購買。
交易費用公式(EIP-1559 之後):
費用 = Gas 使用量 × (基礎費用 + 小費)
其中:
- 基礎費用(Base Fee):由網路根據供需自動調整
- 小費(Priority Fee):給驗證者的額外費用,可設為 0
- Gas 使用量(Gas Used):合約實際執行的 opcode 總消耗
基礎費用計算
EIP-1559 引入的基礎費用機制,會根據上一個區塊的 Gas 使用量自動調整:
目標 Gas 使用量 = 15,000,000(區塊上限的一半)
如果上一個區塊 Gas 使用量 > 目標:
基礎費用 += 基礎費用 × (使用量 - 目標) / 目標 / 8
如果上一個區塊 Gas 使用量 < 目標:
基礎費用 -= 基礎費用 × (目標 - 使用量) / 目標 / 8
這個公式的特點是:無論如何調整,每個區塊的基礎費用變化不會超過 ±12.5%。
Solidity 計算基礎費用:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract GasCalculator {
// 目標區塊 Gas 使用量(區塊上限的一半)
uint256 public constant TARGET_GAS = 15_000_000;
// 區塊上限
uint256 public constant BLOCK_GAS_LIMIT = 30_000_000;
function calculateNextBaseFee(
uint256 currentBaseFee,
uint256 parentGasUsed
) external pure returns (uint256 nextBaseFee) {
if (parentGasUsed > TARGET_GAS) {
// 區塊太滿,增加基礎費用
uint256 gasDelta = parentGasUsed - TARGET_GAS;
// 增加比例上限為 12.5%
uint256 baseFeeChange = currentBaseFee * gasDelta / TARGET_GAS / 8;
nextBaseFee = currentBaseFee + baseFeeChange;
} else {
// 區塊太空,降低基礎費用
uint256 gasDelta = TARGET_GAS - parentGasUsed;
uint256 baseFeeChange = currentBaseFee * gasDelta / TARGET_GAS / 8;
nextBaseFee = currentBaseFee - baseFeeChange;
}
// 基礎費用最低為 1 Wei
if (nextBaseFee < 1) {
nextBaseFee = 1;
}
return nextBaseFee;
}
}
JavaScript 即時 Gas 估算:
const { ethers } = require("ethers");
async function calculateGasFees() {
const provider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID");
// 獲取當前區塊的基礎費用
const feeData = await provider.getFeeData();
console.log("當前 Gas 費用數據:");
console.log(`基礎費用 (Base Fee): ${feeData.baseFeePerGas ? ethers.formatUnits(feeData.baseFeePerGas, 'gwei') : 'N/A'} Gwei`);
console.log(`最大優先費用 (Max PriorityFee): ${feeData.maxPriorityFeePerGas ? ethers.formatUnits(feeData.maxPriorityFeePerGas, 'gwei') : 'N/A'} Gwei`);
console.log(`最大費用上限 (Max Fee): ${feeData.maxFeePerGas ? ethers.formatUnits(feeData.maxFeePerGas, 'gwei') : 'N/A'} Gwei`);
// 計算實際費用
const gasLimit = 21000; // 標準 ETH 轉帳
const estimatedTotal = feeData.maxFeePerGas
? gasLimit * feeData.maxFeePerGas
: gasLimit * feeData.gasPrice;
console.log(`預估總費用: ${ethers.formatEther(estimatedTotal)} ETH`);
// 估算交易確認時間
const currentGasPrice = await provider.getGasPrice();
console.log(`當前 Gas Price: ${ethers.formatUnits(currentGasPrice, 'gwei')} Gwei`);
}
calculateGasFees().catch(console.error);
Python 版本:
from web3 import Web3
import time
def calculate_gas_fees():
w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID"))
# 獲取費用數據
fee_data = w3.eth.fee_history(1, 'latest')
print("當前 Gas 費用數據:")
print(f"區塊基礎費用: {fee_data['baseFeePerGas'][-1] / 10**9:.2f} Gwei")
# 計算平均優先費用
priority_fees = [int.from_bytes(f, 'big') for f in fee_data['reward'][-10:]]
avg_priority = sum(priority_fees) / len(priority_fees) / 10**9
print(f"平均優先費用: {avg_priority:.2f} Gwei")
# 估算費用
gas_limit = 21000 # 標準 ETH 轉帳
base_fee = int(fee_data['baseFeePerGas'][-1])
priority_fee = int(avg_priority * 10**9)
total_fee = gas_limit * (base_fee + priority_fee)
print(f"預估總費用: {w3.from_wei(total_fee, 'ether')} ETH")
# 查詢網路負載
block = w3.eth.get_block('latest')
gas_used_ratio = block['gasUsed'] / block['gasLimit']
print(f"區塊 Gas 使用率: {gas_used_ratio * 100:.1f}%")
# 根據網路負載估算確認時間
if gas_used_ratio > 0.9:
print("網路擁堵,預計確認時間: 5-10 分鐘")
elif gas_used_ratio > 0.5:
print("網路正常,預計確認時間: 1-2 分鐘")
else:
print("網路順暢,預計確認時間: < 1 分鐘")
calculate_gas_fees()
交易 Gas 消耗分解
一筆標準 ETH 轉帳消耗 21,000 Gas。但智能合約交易的 Gas 消耗取決於很多因素。
基礎費用(Intrinsic Gas)
每筆交易都需要支付基礎費用:
基礎費用 = 21000 Gas(固定)+ calldata Gas
calldata Gas 計算:
- 每個非零位元組 = 16 Gas
- 每個零位元組 = 4 Gas
// 計算交易基礎費用(Solidity 示例)
function calculateIntrinsicGas(bytes memory data) public pure returns (uint256) {
uint256 zeroBytes = 0;
uint256 nonZeroBytes = 0;
for (uint256 i = 0; i < data.length; i++) {
if (data[i] == 0) {
zeroBytes++;
} else {
nonZeroBytes++;
}
}
// 基礎轉帳費用
uint256 baseGas = 21000;
// calldata 費用
uint256 calldataGas = zeroBytes * 4 + nonZeroBytes * 16;
return baseGas + calldataGas;
}
EVM Opcode 執行費用
合約內部的 Gas 消耗取決於執行的 Opcode。讓我推導幾個典型場景。
場景一:簡單變數讀寫
// 這段代碼的 Gas 分析
contract SimpleStorage {
uint256 public value;
// 讀取:~2,100 Gas (SLOAD cold)
function read() public view returns (uint256) {
return value;
}
// 寫入:~20,000 Gas (SSTORE 0→非0)
function write(uint256 _value) external {
value = _value;
}
// 讀取 + 寫入:~22,100 Gas
function readAndWrite(uint256 _value) external returns (uint256) {
uint256 oldValue = value; // SLOAD cold: 2100
value = _value; // SSTORE 0→非0: 20000
return oldValue;
}
}
場景二:迴圈操作
contract LoopDemo {
uint256[] public values;
// 迴圈 Gas 消耗推導
// 假設 length = n
function sumValues() external view returns (uint256 sum) {
// 讀取 length: SLOAD cold (2100 Gas)
// 比較 n 次: n × JUMPI (3 Gas)
// 加法 n 次: n × ADD (3 Gas)
// 記憶體分配: MLOAD/MSTORE (3 Gas + memory cost)
// n = 10 時預估:2100 + 30 + 30 + 30 = ~2200 Gas
// n = 100 時預估:2100 + 300 + 300 + 記憶體成本
for (uint256 i = 0; i < values.length; i++) {
sum += values[i];
}
}
// 優化版本:減少 SSTORE
function sumValuesOptimized() external view returns (uint256 sum) {
uint256 len = values.length; // 只 SLOAD 一次
// 使用 assembly 優化迴圈
assembly {
mstore(0x00, 0) // 初始化 sum
for { let i := 0 } lt(i, len) { i := add(i, 1) } {
sum := add(sum, sload(add(values.slot, i)))
}
}
return sum;
}
}
JavaScript 實測 Gas 消耗:
const { ethers } = require("ethers");
async function measureLoopGas() {
const provider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID");
const signer = await provider.getSigner();
const contractABI = [
"function sumValues() view returns (uint256)",
"function sumValuesOptimized() view returns (uint256)"
];
const contractAddress = "YOUR_CONTRACT_ADDRESS";
const contract = new ethers.Contract(contractAddress, contractABI, signer);
// 設定測試資料
const setDataTx = await contract.runner.sendTransaction({
to: contractAddress,
data: contract.interface.encodeFunctionData("values", [[1,2,3,4,5,6,7,8,9,10]])
});
await setDataTx.wait();
// 估算 Gas(不發送交易)
const gasNormal = await contract.sumValues.estimateGas();
const gasOptimized = await contract.sumValuesOptimized.estimateGas();
console.log(`普通版本 Gas: ${gasNormal}`);
console.log(`優化版本 Gas: ${gasOptimized}`);
console.log(`節省: ${gasNormal - gasOptimized} Gas (${((gasNormal - gasOptimized) / gasNormal * 100).toFixed(1)}%)`);
}
measureLoopGas().catch(console.error);
Python 版本:
from web3 import Web3
def measure_loop_gas():
w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID"))
contract_address = "YOUR_CONTRACT_ADDRESS"
abi = [
{"inputs": [], "name": "sumValues", "outputs": [{"type": "uint256"}],
"stateMutability": "view", "type": "function"},
{"inputs": [], "name": "sumValuesOptimized", "outputs": [{"type": "uint256"}],
"stateMutability": "view", "type": "function"}
]
contract = w3.eth.contract(address=contract_address, abi=abi)
# 估算 Gas
gas_normal = contract.functions.sumValues().estimate_gas()
gas_optimized = contract.functions.sumValuesOptimized().estimate_gas()
print(f"普通版本 Gas: {gas_normal}")
print(f"優化版本 Gas: {gas_optimized}")
print(f"節省: {gas_normal - gas_optimized} Gas")
# 計算費用差異
gas_price = w3.eth.gas_price
fee_diff_normal = gas_normal * gas_price
fee_diff_optimized = gas_optimized * gas_price
print(f"費用差異: {w3.from_wei(fee_diff_normal - fee_diff_optimized, 'ether')} ETH")
measure_loop_gas()
記憶體擴展成本模型
EIP-2028 之後,記憶體成本採用了新的計算模型:
C_mem(a) = 3 × a + floor(a² / 512)
其中 a = memoryUsageInWords = ceil(memoryBytes / 32)
這個模型的特點是:記憶體使用量小的時候,成本接近線性增長;當記憶體使用量變大,成本增長速度會越來越快。
記憶體成本推導表
記憶體大小(Bytes)│ Words │ 總 Gas 成本
─────────────────┼───────┼─────────────
0 │ 0 │ 0
32 │ 1 │ 3
64 │ 2 │ 6
128 │ 4 │ 14
256 │ 8 │ 38
512 │ 16 │ 98
1,024 │ 32 │ 266
2,048 │ 64 │ 716
4,096 │ 128 │ 2,342
8,192 │ 256 │ 7,414
16,384 │ 512 │ 22,874
32,768 │ 1024 │ 86,354
你可以看到,當記憶體從 1KB 增加到 32KB 時,成本從 266 Gas 暴漲到 86,354 Gas。
Solidity 記憶體成本實驗:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract MemoryCostDemo {
// 不同記憶體使用量的成本測試
function allocateSmall() public pure returns (uint256) {
uint256[1] memory arr; // 32 bytes
arr[0] = 42;
return arr[0];
}
function allocateMedium() public pure returns (uint256) {
uint256[100] memory arr; // 3,200 bytes
uint256 sum = 0;
for (uint256 i = 0; i < 100; i++) {
arr[i] = i;
sum += arr[i];
}
return sum;
}
function allocateLarge() public pure returns (uint256) {
uint256[1000] memory arr; // 32,000 bytes
uint256 sum = 0;
for (uint256 i = 0; i < 1000; i++) {
arr[i] = i;
sum += arr[i];
}
return sum;
}
}
SSTORE 成本:狀態寫入的代價
SSTORE 是以太坊上最貴的操作之一。理解它的成本模型對 Gas 最佳化至關重要。
SSTORE 成本規則
將值從 0 寫成非0:20000 Gas
將值從非0 改成非0:2900 Gas
將值從非0 寫成 0: 2900 Gas - 退款 10000 Gas
將值從 0 寫成 0: 200 Gas
退款機制是為了獎勵「清理狀態」的行為,但退款上限是總消耗的一半。
實務推導
contract SSToreCostDemo {
uint256 public value;
uint256 public value2;
// 場景1:首次寫入
// Gas = 21000 (tx) + 2100 (cold SLOAD) + 20000 (SSTORE) = ~43100
function firstWrite() external {
value = 42; // 20000 Gas
}
// 場景2:更新現存值
// Gas = 21000 (tx) + 2100 (cold SLOAD) + 100 (hot SLOAD) + 2900 (SSTORE) = ~24000
function updateExisting() external {
value = 100; // 2900 Gas (非0→非0)
}
// 場景3:清除值(獲得退款)
// Gas = 21000 + 2100 + 100 + 2900 = 24100
// 退還 = 10000 Gas
// 實際净消耗 = 14100 Gas
function clearValue() external {
delete value; // 退還 10000 Gas
}
// 場景4:批量操作與退款上限
// 如果一次性清除很多值,退款會被限制
function batchClear() external {
delete value; // 退還 10000
delete value2; // 退還 10000
// 但總退款上限 = 總消耗 / 2
}
}
跨合約呼叫成本
現代 DeFi 合約幾乎都會跨合約呼叫。理解這部分成本很重要。
CALL Opcode 成本
CALL 冷啟動:2600 Gas + memory cost
CALL 熱啟動:100 Gas + memory cost
被呼叫合約還需要支付自己的執行費用,這部分由呼叫方預付。
contract CrossContractDemo {
// ERC20 代幣介面
IERC20 public token;
constructor(address _token) {
token = IERC20(_token);
}
// 低效:每次迴圈都呼叫
function batchTransferInefficient(
address[] calldata recipients,
uint256[] calldata amounts
) external returns (bool) {
for (uint256 i = 0; i < recipients.length; i++) {
// 每一次迴圈都是一次完整的 CALL
token.transfer(recipients[i], amounts[i]);
}
return true;
}
// 高效:使用 multicall 批次
function multicall(
bytes[] calldata data
) external returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
// 只執行一次外部呼叫
(bool success, bytes memory result) = address(token).delegatecall(data[i]);
require(success, "Call failed");
results[i] = result;
}
}
}
JavaScript 批次呼叫優化:
const { ethers } = require("ethers");
async function optimizeCrossContractCalls() {
const provider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID");
const signer = await provider.getSigner();
// Multicall3 合約(最常用的批次呼叫合約)
const multicallAddress = "0xcA11bde05977b3631167028862bE2a173976CA11";
const erc20ABI = [
"function balanceOf(address) view returns (uint256)",
"function transfer(address, uint256) returns (bool)"
];
const multicall = new ethers.Contract(
multicallAddress,
["function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[])"],
provider
);
const tokenAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC
const userAddress = await signer.getAddress();
// 批次查詢:5 個地址的代幣餘額
const targets = [tokenAddress, tokenAddress, tokenAddress, tokenAddress, tokenAddress];
const balanceOfIface = new ethers.Interface(erc20ABI);
const calls = targets.map((target, i) => ({
target,
allowFailure: false,
callData: balanceOfIface.encodeFunctionData("balanceOf", [userAddress])
}));
// 單一 RPC 呼叫獲取 5 個餘額
console.log("執行批次查詢...");
const startTime = Date.now();
const results = await multicall.aggregate3.staticCall(calls);
const endTime = Date.now();
console.log(`批次查詢耗時: ${endTime - startTime}ms`);
console.log(`節省約: ${(calls.length - 1) * 100}ms (相對於逐個查詢)`);
// 解析結果
results.forEach((result, i) => {
const balance = ethers.formatUnits(result.returnData, 6); // USDC 6 位小數
console.log(`查詢 #${i + 1}: ${balance} USDC`);
});
}
optimizeCrossContractCalls().catch(console.error);
Python Multicall 實作:
from web3 import Web3
from eth_abi import encode
def multicall_balance():
w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID"))
multicall_address = "0xcA11bde05977b3631167028862bE2a173976CA11"
# Multicall3 合約 ABI
multicall_abi = [{
"inputs": [{
"components": [
{"name": "target", "type": "address"},
{"name": "allowFailure", "type": "bool"},
{"name": "callData", "type": "bytes"}
],
"name": "calls",
"type": "tuple[]"
}],
"name": "aggregate3",
"outputs": [{
"components": [
{"name": "success", "type": "bool"},
{"name": "returnData", "type": "bytes"}
],
"name": "",
"type": "tuple[]"
}],
"stateMutability": "payable",
"type": "function"
}]
multicall = w3.eth.contract(address=multicall_address, abi=multicall_abi)
# ERC20 代幣合約
tokens = [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC
"0xdAC17F958D2ee523a2206206994597C13D831ec7", # USDT
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH
]
wallet_address = "0x742d35Cc6634C0532925a3b844Bc9e7595f5b5b0"
# 建構批次呼叫
calls = []
for token_address in tokens:
token_abi = [{
"inputs": [{"name": "owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"type": "uint256"}],
"stateMutability": "view",
"type": "function"
}]
token = w3.eth.contract(address=token_address, abi=token_abi)
# 編碼函數調用
call_data = token.functions.balanceOf(wallet_address).build_transaction()['data']
calls.append({
'target': token_address,
'allowFailure': False,
'callData': call_data
})
# 執行批次呼叫
result = multicall.functions.aggregate3(calls).call()
print("批次查詢結果:")
for i, token_address in enumerate(tokens):
if result[i]['success']:
balance = int.from_bytes(result[i]['returnData'], 'big')
decimals = 6 if 'USDC' in str(tokens[i]) or 'USDT' in str(tokens[i]) else 18
symbol = "USDC" if 'USDC' in str(tokens[i]) else ("USDT" if 'USDT' in str(tokens[i]) else "WETH")
print(f"{symbol}: {balance / 10**decimals:.4f}")
multicall_balance()
Gas 退款機制
EIP-3529 修改了 Gas 退款規則:
退款上限 = (初始 Gas - 消耗的 Gas) / 2
這意味著你的退款永遠不會超過你消耗的一半。
contract RefundDemo {
uint256 public counter;
uint256[] public largeArray;
constructor() {
// 初始化一個大陣列(消耗 Gas)
for (uint256 i = 0; i < 1000; i++) {
largeArray.push(i);
}
}
// 清除陣列(獲得大量退款)
// 假設 initialGas = 100000
// 清除消耗 = 2900 * 1000 = 2,900,000
// 退還 = 10000 * 1000 = 10,000,000
// 但退款上限 = (100000 - 2900000) / 2... 為負數
// 實際退款 = 100000 / 2 = 50000
function clearArray() external {
delete largeArray;
}
// 更好的做法:分批清理
function clearArrayBatched(uint256 batchSize) external {
require(largeArray.length >= batchSize, "Not enough elements");
for (uint256 i = 0; i < batchSize; i++) {
largeArray.pop();
}
// 每次刪除都計算退款
}
}
實用 Gas 估算工具
手動估算模式
// 完整 Gas 估算流程
const { ethers } = require("ethers");
async function fullGasEstimation() {
const provider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID");
const contractAddress = "YOUR_CONTRACT_ADDRESS";
const abi = [
"function myFunction(uint256[] calldata values) external returns (uint256)"
];
const contract = new ethers.Contract(contractAddress, abi, provider);
// 1. 估算執行 Gas
const gasEstimate = await contract.myFunction.estimateGas([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
]);
console.log(`執行 Gas 估算: ${gasEstimate}`);
// 2. 獲取當前費用數據
const feeData = await provider.getFeeData();
console.log(`基礎費用: ${feeData.baseFeePerGas ? ethers.formatUnits(feeData.baseFeePerGas, 'gwei') : 'N/A'} Gwei`);
console.log(`優先費用: ${feeData.maxPriorityFeePerGas ? ethers.formatUnits(feeData.maxPriorityFeePerGas, 'gwei') : 'N/A'} Gwei`);
// 3. 計算最大費用
const maxFeePerGas = feeData.maxFeePerGas || feeData.gasPrice;
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas || ethers.parseUnits("1", "gwei");
// 4. 計算總費用
const gasLimit = gasEstimate + 10000; // 加上 buffer
const totalCost = gasLimit * maxFeePerGas;
console.log(`建議 Gas 上限: ${gasLimit}`);
console.log(`預估總費用: ${ethers.formatEther(totalCost)} ETH`);
// 5. 估算礦工收入
const minerReward = gasLimit * maxPriorityFeePerGas;
console.log(`礦工小費: ${ethers.formatEther(minerReward)} ETH`);
}
fullGasEstimation().catch(console.error);
Python Gas 估算系統
from web3 import Web3
import time
class GasEstimator:
def __init__(self, provider_url):
self.w3 = Web3(Web3.HTTPProvider(provider_url))
def estimate_transaction(self, contract_address, function_name, *args):
"""估算交易 Gas"""
# 讀取代碼(用於估算)
code = self.w3.eth.get_code(contract_address)
print(f"合約位元組碼大小: {len(code)} bytes")
# 估算 Gas
# 這需要根據具體合約調整
gas_estimate = 100000 # 預設值
return gas_estimate
def calculate_fee(self, gas_estimate):
"""計算費用"""
fee_data = self.w3.eth.fee_history(1, 'latest')
base_fee = int(fee_data['baseFeePerGas'][-1])
priority_fee = int(self.w3.eth.max_priority_fee())
max_fee = base_fee + priority_fee
total_fee_wei = gas_estimate * max_fee
return {
'base_fee_gwei': base_fee / 10**9,
'priority_fee_gwei': priority_fee / 10**9,
'max_fee_gwei': max_fee / 10**9,
'total_fee_eth': self.w3.from_wei(total_fee_wei, 'ether'),
'total_fee_usd': self.w3.from_wei(total_fee_wei, 'ether') * 3500 # 假設 ETH = $3500
}
def recommend_gas_settings(self, urgency='normal'):
"""根據緊急性推薦 Gas 設定"""
fee_data = self.w3.eth.fee_history(5, 'latest')
avg_base_fee = sum(int(f) for f in fee_data['baseFeePerGas']) / len(fee_data['baseFeePerGas'])
if urgency == 'fast':
multiplier = 1.5
elif urgency == 'slow':
multiplier = 0.8
else:
multiplier = 1.1
priority_fee = int(self.w3.eth.max_priority_fee())
return {
'base_fee_gwei': avg_base_fee / 10**9,
'priority_fee_gwei': priority_fee / 10**9,
'max_fee_gwei': int(avg_base_fee * multiplier + priority_fee) / 10**9
}
# 使用範例
estimator = GasEstimator("https://mainnet.infura.io/v3/YOUR_PROJECT_ID")
settings = estimator.recommend_gas_settings('normal')
print(f"推薦 Gas 設定: {settings}")
Gas 最佳化實務技巧
技巧一:善用熱讀取優惠
// 不好的代碼
function bad(address user) external {
if (balance[user] > 0 && balance[user] > minAmount) {
// ...
}
// 第二次讀取 balance[user]
if (balance[user] > 0) {
// ...
}
}
// 好的代碼
function good(address user) external {
uint256 userBalance = balance[user]; // 一次讀取
if (userBalance > 0 && userBalance > minAmount) {
// ...
}
if (userBalance > 0) { // 使用快取
// ...
}
}
技巧二:使用 events 而不是回傳值
// 低效
function getValue() external view returns (uint256) {
return storedValue; // SLOAD
}
// 高效
event ValueChanged(uint256 newValue);
function setValue(uint256 _value) external {
storedValue = _value;
emit ValueChanged(_value); // LOG 比 SSTORE 便宜
}
技巧三:避免在迴圈中進行 SLOAD
// 不好的代碼
function bad(address[] calldata users) external {
for (uint256 i = 0; i < users.length; i++) {
// 每個迴圈都 SLOAD
if (balances[users[i]] > threshold) {
// ...
}
}
}
// 好的代碼
function good(address[] calldata users) external {
uint256 len = users.length;
uint256[] memory cachedBalances = new uint256[](len);
// 批量讀取
for (uint256 i = 0; i < len; i++) {
cachedBalances[i] = balances[users[i]];
}
// 使用快取
for (uint256 i = 0; i < len; i++) {
if (cachedBalances[i] > threshold) {
// ...
}
}
}
結語:Gas 是以太坊的語法
搞懂了 Gas,你就搞懂了以太坊的一半。
這篇文章涵蓋了 Gas 計算的核心概念:基礎費用模型、Opcode 消耗、記憶體成本、SSTORE 機制、跨合約呼叫,以及實用的最佳化技巧。這些知識不只能幫你省 Gas,更重要的是讓你能寫出更高效的合約。
記住:每次合約執行都有代價,每筆狀態寫入都要付費。當你能用成本的角度思考合約設計,你就離優秀的以太坊工程師更近一步了。
資料截止日期:2026 年 3 月
一級來源:
- Ethereum Yellow Paper: https://ethereum.github.io/yellowpaper/paper.pdf
- EIP-1559 規格: https://eips.ethereum.org/EIPS/eip-1559
- EIP-2028 規格: https://eips.ethereum.org/EIPS/eip-2028
- EIP-3529 規格: https://eips.ethereum.org/EIPS/eip-3529
二級來源:
- Solidity 文件: https://docs.soliditylang.org/
- ethereum.org Gas 指南: https://ethereum.org/developers/docs/gas
- Vitalik 的 Gas 成本分析: https://vitalik.ca/
工具:
- Gas Tracker: https://etherscan.io/gastracker
- Gas Now: https://gasnow.xyz/
相關文章
- EVM Gas 計算深度技術分析:從理論到 Uniswap V4 Hook 與 AAVE V4 真實合約的 Gas 優化實戰 — 本文深入分析 EVM Gas 計算的根本邏輯,涵蓋坎昆升級後的 Gas 模型變化、Uniswap V4 Hook 合約的常見 Gas 陷阱(如未快取的跨合約呼叫讀取、動態陣列處理)、以及 AAVE V4 風險引擎中的 Gas 優化密技。從真實 DeFi 協議原始碼出發,提供可操作的 Gas 優化技巧,包括 storage packing、反模式修正、以及 EVM opcode 層級的成本分析。
- 以太坊 EVM Gas 計算數學推導:從 opcode 到複雜合約的深度量化分析 — 本文深入探討以太坊 EVM Gas 計算的數學原理,提供完整的 opcode 級別成本推導、SSTORE 的冷熱存儲成本模型、CALL 指令的複雜成本計算、以及 EIP-1559 費用模型的數學推導。包含大量 Python 程式碼範例和實際部署成本分析,幫助開發者理解 Gas 消耗的底層邏輯。
- 以太坊 EVM Opcodes 完整參考手冊:Gas 消耗數學推導與實戰最佳化指南 — 本文深入剖析 EVM 完整 opcode 指令集,從基礎的算術運算到複雜的存儲操作,提供完整的 Gas 消耗數學推導與實戰最佳化指南。涵蓋記憶體成本二次函數推導、SSTORE 狀態機制、CALL 系列的 cold/warm access 定價模型、日誌操作的 Gas 計算、以及 EOF 時代新 opcode 的完整解析。提供大量 Solidity 和 Assembly 程式碼範例,幫助開發者編寫更省 Gas 的智能合約。
- EVM 內部運作與 Gas 優化完整攻略:從原始碼到實際應用 — 搞區塊鏈開發的人,十個有九個被 Gas 搞過心態爆炸。明明同一個功能,別人的合約就是比你便宜一半,區塊空間用得漂亮,交易被打包的優先順序還比你高。這篇文章帶你從 EVM 到底怎麼執行 Opcode 講起,搞懂 Gas 的計費模型、儲存讀寫的成本差異、並實際展示十幾種可以立刻用在專案裡的優化技巧。
- EVM Opcode 執行成本與 Gas 消耗深度技術分析:以太坊黃皮書規範引用與實際執行案例 — 本文深入分析以太坊虛擬機器(EVM)各類 Opcode 的 Gas 消耗模型,基於以太坊黃皮書的正式規範,提供每個操作碼的數學計算公式、複雜度分析以及實際執行成本案例。研究涵蓋從最基礎的棧操作到複雜的密碼學計算,幫助開發者建立精確的 Gas 估算能力。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案完整列表
- Solidity 文檔 智慧合約程式語言官方規格
- EVM 代碼庫 EVM 實作的核心參考
- Alethio EVM 分析 EVM 行為的正規驗證
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!