EVM 的每一条指令背后都有 Gas 成本,它决定了你的智能合约“贵”还是“省”。本篇系统解析 EVM 指令的 Gas 分类、Solidity 中常见高耗模式、以及如何用工具进行成本调试与优化,是编写高性能智能合约的必修课。
📚 作者:Henry
🧱 系列:《深入理解区块链 Gas 机制》 · 第 3 篇
👨💻 受众:Web3 开发者 / Solidity 工程师 / 区块链学习者
理解底层 Gas 构成,有助于:
类别 | 示例指令 | Gas 成本特点 |
---|---|---|
运算操作 | ADD , MUL |
低,一般在 3 ~ 5 Gas |
环境查询 | CALLER , TIMESTAMP |
中等,10 ~ 50 Gas |
内存操作 | MLOAD , MSTORE |
随内存扩展而增长 |
存储操作 | SLOAD , SSTORE |
非常昂贵(写操作高达 20,000) |
外部调用 | CALL , DELEGATECALL |
可变,受转账与 calldata 大小影响 |
控制流 | JUMP , STOP |
较低 |
合约生命周期 | CREATE , SELFDESTRUCT |
非常昂贵 |
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value; // 每次写入都触发 SSTORE
}
优化建议:避免频繁写 storage,可用事件记录或结构优化。
event Deposit(address indexed user, uint256 amount);
function deposit() external payable {
emit Deposit(msg.sender, msg.value); // 更便宜的日志记录
}
📌 用事件替代链上写入,适合链下读取或状态延迟合约。
function updateBatch(uint256[] memory values) public {
for (uint i = 0; i < values.length; i++) {
data[i] = values[i];
}
}
bytes32 public batchHash;
function updateBatch(bytes calldata packedData) external {
batchHash = keccak256(packedData); // 单次写入 storage
}
📌 将多值批量写入转换为摘要写入,节省 Gas 成本。
function batchCall(address[] memory targets) external {
for (uint i = 0; i < targets.length; i++) {
targets[i].call("");
}
}
interface IBatchable {
function execute() external;
}
function batchExecute(address[] calldata targets) external {
for (uint i = 0; i < targets.length; i++) {
IBatchable(targets[i]).execute();
}
}
📌 合并数据结构,使用标准接口统一编码,降低 calldata 复杂度。
CALL
的 Gas 构成每 32 字节为一“字”,EVM 计算内存扩展成本如下:
memory_cost = (memory_word_size^2) / 512 + (3 * memory_word_size)
hardhat-gas-reporter
本篇我们了解了:
掌握这些细节,可以帮助开发者写出更便宜、更稳健的智能合约。
《Base Fee 是如何动态变化的?理解以太坊的 EIP-1559 机制》
将深入解析以太坊的手续费模型演进,解释 Base Fee、Priority Fee 的设置原理,以及未来如何通过 Layer2 实现更极致的 Gas 优化。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!