本文作者:小驹[1]
1. 简介
以太坊编程中的存储主要包括两种:
- 以太坊如何在区块链上存储合约数据
- Solidity 如何存储全局变量和局部变量。
每个智能合约都有自己的存储来反映合约的状态,这些存储都与智能合约的地址进行绑定。在不同的函数调用中,这些存储中的值都是保持不变的。
1.1 存储的基本原则
本文主要讨论在区块链上存储的合约数据。根据官方文档,合约数据在以太坊区块链上有 2^256 个槽,每个槽 32 字节.
Storage on Ethereum blockchain is 2^256 slots, and each slot is 32 bytes. 在以太坊区块链中的存储有 2^256 个槽,每个槽 32 字节。
静态变量(除了映射和动态大小的数组类型之外的所有变量)从位置 0 开始在存储中连续布局。同时为了节省空间,会根据以下规则将需要少于 32 个字节的多个项目打包到一个存储槽中:
- 在每个槽中,第一项存储在低位,第二项存储在次低位,从低位向高位存储。
- 基本类型只使用存储它们所需的那么多字节,如一个 bool 只使用 1 个字节,1 个 uint16 只使用 2 个字节。
- 如果一个存储槽的剩余空间不足以存储基本类型,则将该基本类型移动到下一个存储槽中存储。
- 结构休和数组总是开始一个新的槽并占据整个槽(但是结构体或数组中的子类型也会根据这些上面的规则被优化存储)。
数据按声明顺序依次存储在这些插槽中。存储时会进行优化以节省存储空间。因此,如果依次的多个变量可以在单个 32 字节槽中容纳的话,它们将共享同一个槽,并且依次从最低有效位(从右侧)开始存储和索引。
太坊存储和空间优化的可视化示例。图 1 说明状态变量按顺序挨个slot存储
,图 2 说明会将变量尽可能“挤”在一个slot中
,“挤” 不下的话就存储在下一个 slot 中。
当使用小于 32 字节的类型时,所花费合约的 gas 可能会更高。这与直观感受的”使用的空间越少,gas 费越低”的直观感受不相符。这是因为 EVM 一次运行 32 个 bytes。因此,如果类型小于 32 个 bytes,EVM 必须使用更多的操作才能将类型的大小从 32 bytes 减少到所需的大小。只有在存储内容时,使用对应大小的的参数是效果的,因为编译器会将多个元素打包到一个存储槽中,从而将多个读取或写入组合到一个操作中。在处理函数参数或 memory 类型值时,尽量不要使用小于 32 的类型,因为编译器不会打包这些值。由于它们不可预测的大小,映射和动态大小的数组类型使用 Keccak-256 哈希计算来查找值或数组数据的起始位置。这些起始位置始终是满栈槽。
1.2 动态数组和 mapping 类型的存储
mapping 和动态数组的大小不可预测,因此映射和动态数组类型使用 Keccak256 哈希
计算来查找值或数组数据的起始位置。这些起始位置始终是放在一个槽中。