0x01 存储优化为什么重要
最近帮朋友优化一个 NFT 合约,发现一个有意思的现象:仅仅调整了几个状态变量的顺序,mint 成本就从 180,000 gas 降到了 145,000 gas,省了将近 20%。
这就是存储布局优化的魅力。
在以太坊中,存储(storage)是最贵的资源之一:
- 首次写入一个 slot (32 字节):20,000 gas
- 修改现有 slot:5,000 gas
- 读取 slot:200-2100 gas
对于高频调用的合约(DeFi、NFT),这些成本会迅速累积。好消息是,通过合理的变量布局,我们可以显著降低这些开销。
0x02 EVM 的存储规则
EVM 的存储是一个巨大的 key-value 数组,每个 slot 是 32 字节。
Solidity 的存储布局规则:
- 状态变量按声明顺序依次分配 slot
- 小于 32 字节的变量会尝试打包到同一个 slot
- 固定大小的数组和结构体会连续占用 slots
看一个例子:
// ❌ 未优化版本
contract BadStorage {
uint256 totalSupply; // slot 0
uint8 decimals; // slot 1 (浪费 31 字节)
uint256 maxSupply; // slot 2
address owner; // slot 3 (浪费 12 字节)
bool paused; // slot 4 (浪费 31 字节)
}
这个合约用了 5 个 slots。
0x03 打包优化
将小变量放在一起,让它们共享 slot:
// ✅ 优化版本
contract GoodStorage {
uint256 totalSupply; // slot 0
uint256 maxSupply; // slot 1
address owner; // slot 2 (20 字节)
uint8 decimals; // slot 2 (1 字节) ← 打包
bool paused; // slot 2 (1 字节) ← 打包
}
现在只用 3 个 slots,省了 40%!
规则总结:
- 把 uint256 这类 32 字节的变量放一起
- 把小变量(address、uint8、bool)放一起
- 尽量让打包的变量在逻辑上相关(一起读写)
0x04 实际案例:NFT 合约
我优化的那个 NFT 合约,原始代码:
contract NFTBefore {
uint256 public totalSupply;
address public owner;
uint256 public maxSupply;
bool public paused;
uint8 public maxPerWallet;
uint256 public price;
}
// 6 个 slots
优化后:
contract NFTAfter {
uint256 public totalSupply;
uint256 public maxSupply;
uint256 public price;
address public owner; // 20 字节
bool public paused; // 1 字节 } slot 3
uint8 public maxPerWallet; // 1 字节 }
}
// 4 个 slots
Gas 对比(mint 函数):
| 版本 | 部署 Gas | Mint Gas |
|---|---|---|
| 优化前 | 842,000 | 180,000 |
| 优化后 | 756,000 | 145,000 |
省下的钱在 L1 上很可观,L2 上感知弱一些,但习惯养成还是很重要的。
0x05 常见陷阱
陷阱 1:结构体破坏打包
// ❌ 这样写会阻止打包
address owner;
MyStruct data; // 结构体会从新 slot 开始
bool paused;
// ✅ 应该这样
address owner;
bool paused; // 先完成打包
MyStruct data; // 结构体再开始
陷阱 2:过度优化可读性
不要为了省 gas 而牺牲代码可读性。如果两个变量逻辑上不相关,即使能打包也不建议放一起,否则维护时容易出错。
陷阱 3:忽略动态数组
mapping 和动态数组(uint256[])的存储位置是通过哈希计算的,它们本身只占一个 slot 存引用,实际数据在别处。
0x06 实践建议
用工具检查:
forge inspect YourContract storage可以看到完整的存储布局测试 gas:优化前后都要跑测试,量化收益
文档化布局:在复杂合约中,注释清楚哪些变量在同一个 slot
// slot 0: totalSupply
// slot 1: maxSupply
// slot 2: owner (20B) | paused (1B) | maxPerWallet (1B)
- 继承时注意:子合约的变量会接着父合约的 slot 继续排列,可能破坏打包
0x07 总结
存储优化是 Solidity 开发中容易被忽视但回报很高的技巧。核心就是理解 EVM 的 32 字节 slot 机制,合理安排变量顺序。
记住三个原则:
- 大变量聚合,小变量打包
- 逻辑相关的变量放一起
- 用工具验证,用测试量化
在 gas 就是钱的世界里,每个 slot 都值得优化。