Solidity的存储结构简介以太坊智能合约有三种数据存储位置
以太坊智能合约有三种数据存储位置:
基础类型变量包括:uint256、address、bool 等。 contract SimpleStorage { uint256 a; // slot 0 address b; // slot 1 bool c; // slot 2 }
当多个较小的变量(例如:uint8, uint16, bool 等)连续声明时,编译器会将多个变量紧密打包到同一个槽位中,以节约空间: contract PackedStorage { uint128 a; // slot 0 (前16字节) uint64 b; // slot 0 (中间8字节) uint64 c; // slot 0 (最后8字节) uint256 d; // slot 1 (新槽位) } 紧密打包规则:
动态类型变量主要包括:
动态长度数组(如:uint256[])
mapping 映射(如:mapping(address => uint256))
bytes 和 string 类型
槽位本身(slot 0):存储数组长度;
数组元素:元素从槽位 keccak256(slot) 开始存储,连续排列:元素 i 的槽位位置 = keccak256(slot) + i 例如:
arr.length 存储于 slot 0;
arr[0] 存储于 slot: keccak256(0);
arr[1] 存储于 slot: keccak256(0) + 1;
arr[2] 存储于 slot: keccak256(0) + 2;以此类推。
2.mapping 存储布局 mapping 是动态哈希表: contract MappingStorage { mapping(uint256 => uint256) map; // slot 0 }
槽位本身(slot 0): 不存储任何实际数据,仅用于哈希计算起点;
每个键值对存储位置计算方式: slot(key) = keccak256(abi.encode(key, slot)) 例如:
map[5] 存储在 keccak256(abi.encode(5, 0));
map[100] 存储在 keccak256(abi.encode(100, 0))。
3.bytes 和 string 存储布局 bytes 和 string 类型本质上也是动态数组,其存储机制为:
如果长度小于等于31字节:
如果长度大于31字节:
contract StructStorage {
struct Person {
uint256 id; // slot 0
address addr; // slot 1
uint128 age; // slot 2 (前半)
uint128 score;// slot 2 (后半)
}
Person p; // 从 slot 0 开始排列(结构体内变量依次排列)
}
结构体:
复杂数据结构(如结构体数组、mapping 嵌套数组)存储布局,简单举例: contract NestedStorage { mapping(uint => uint[]) nested; // slot 0 } 计算位置步骤:mapping 元素位置计算方式: nested[key] => keccak256(abi.encode(key, 0))
变量类型 | 存储位置计算方式 |
---|---|
基础类型(uint256) | 直接分配槽位 |
动态数组 | 长度在槽位本身,元素存储从 keccak256(slot) 开始连续存储 |
mapping | 数据存储位置 = keccak256(abi.encode(key, slot)) |
bytes/string (≤31B) | 槽位内紧密存储 |
bytes/string (>31B) | 槽位存长度标记,数据从 keccak256(slot) 开始存储 |
结构体 | 成员依次存储,遵循紧密打包原则 |
在智能合约安全审计、调试、链上数据读取过程中,经常用到明确的 slot 定位和计算,比如:
例如定义 mapping(uint256 => uint256) public balances; // slot 3 计算 balances[999] 的具体位置
bytes32 location = keccak256(abi.encode(uint256(999), uint256(3))); 通过 EVM 读取此位置数据 assembly { let value := sload(location) }
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!