Solidity 存储布局优化:省下的每个 slot 都是钱

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%!

规则总结:

  1. 把 uint256 这类 32 字节的变量放一起
  2. 把小变量(address、uint8、bool)放一起
  3. 尽量让打包的变量在逻辑上相关(一起读写)

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 实践建议

  1. 用工具检查forge inspect YourContract storage 可以看到完整的存储布局

  2. 测试 gas:优化前后都要跑测试,量化收益

  3. 文档化布局:在复杂合约中,注释清楚哪些变量在同一个 slot

// slot 0: totalSupply
// slot 1: maxSupply  
// slot 2: owner (20B) | paused (1B) | maxPerWallet (1B)
  1. 继承时注意:子合约的变量会接着父合约的 slot 继续排列,可能破坏打包

0x07 总结

存储优化是 Solidity 开发中容易被忽视但回报很高的技巧。核心就是理解 EVM 的 32 字节 slot 机制,合理安排变量顺序。

记住三个原则:

  • 大变量聚合,小变量打包
  • 逻辑相关的变量放一起
  • 用工具验证,用测试量化

在 gas 就是钱的世界里,每个 slot 都值得优化。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容