以太坊作为全球领先的智能合约平台,其核心之一是能够支持复杂的应用逻辑和持久化状态数据,这一切的背后,以太坊虚拟机(EVM)的存储机制扮演着至关重要的角色,EVM存储不仅是智能合约执行结果的载体,更是整个以太坊状态树的关键组成部分,理解EVM的存储机制,对于智能合约开发者、优化者以及任何希望深入以太坊工作原理的人来说,都是必不可少的一课。
什么是EVM存储?
EVM存储,通常我们指的是合约的“永久存储”(Permanent Storage),也称为“状态存储”(State Storage),它是一个位于以太坊状态 trie 中的特定键值对数据库,每个智能合约都拥有自己独立的存储空间。
- 数据持久性:存储在EVM存储中的数据会在交易之间持续存在,除非被显式修改或删除,这与EVM的“内存”(Memory)和“栈”(Stack)形成了鲜明对比,后两者都是临时性的,仅在合约执行期间存在。
- 键值对结构:每个合约的存储空间是一个从256位(32字节)整数(键)到256位整数(值)的映射,键和值都是以太坊中的基本数据类型。
- 全局状态的一部分:所有合约的存储共同构成了以太坊全局状态树(State Trie)的一个子集,即存储树(Storage Trie),这个状态树是以太坊区块链数据完整性的核心。
EVM存储的工作原理
-
存储访问与修改:
- 当智能合约需要读取或写入存储时,它会通过特定的EVM操作码(如
SLOAD用于读取,SSTORE用于写入)与存储进行交互。 - SLOAD (Storage Load):从合约的存储中读取指定键对应的值,读取操作会消耗一定的 gas。
- SSTORE (Storage Store):将一个值写入合约存储的指定键,写入操作是 EVM 中最昂贵的操作之一,其 gas 消费取决于值的变化情况(从零到非零,或从非零到零/另一个非零值)。
- 当智能合约需要读取或写入存储时,它会通过特定的EVM操作码(如
-
存储布局:
- 以太坊合约的存储在底层是以“槽位”(Slot)为单位组织的,每个槽位大小为32字节(256位)。
- 基本数据类型:对于
uint256,int256,address等基本类型,它们通常直接占用一个完整的槽位,如果类型小于32字节(如bool,uint8),它们会被打包到同一个槽位中,以节省空间。 - 数组:对于动态大小的数组(如
uint256[]),数组的长度存储在槽位0,而实际的数组元素从槽位1开始连续存储(每个元素一个槽位),对于静态大小的数组,元素从槽位0开始连续存储。 - 结构体(Struct)和映射(Mapping):结构体的字段按照顺序依次存储在连续的槽位中,映射(Mapping)则更为复杂,其键的 Keccak-256 哈希值与存储槽位的偏移量共同确定实际存储值的槽位位置。
mapping(uint256 => uint256)的值v对应键k的存储位置为keccak256(k)。
-
Gas 成本机制:
- EVM 存储的 gas 设计旨在鼓励高效使用存储,并防止滥用,写入存储的 gas 成本较高,并且有“冷访问”和“热访问”之分(在 EIP-2929 之后)。
- 初始写入(SSTORE):将一个槽位从“未初始化”(cold)或“零值”写入“非零值”,成本最高。
- 修改值(SSTORE):将一个已存在的非零值槽位修改为另一个非零值,成本较低。
- 清除值(SSTORE):将一个非零值槽位重置为零值,成本介于两者之间。
- 读取(SLOAD):首次访问一个槽位(冷访问)比后续访问(热访问)成本高。
- 这些 gas 成本的设计是为了确保存储空间的合理使用,避免恶意合约消耗过多网络资源。
EVM存储的重要性与影响