| title | 01. 基础存储布局 | ||||
|---|---|---|---|---|---|
| tags |
|
《WTF Solidity内部标准》教程将介绍Solidity智能合约中的存储布局,内存布局,以及ABI编码规则,帮助大家理解Solidity的内部规则。
所有代码和教程开源在github: github.com/AmazingAng/WTF-Solidity-Internals
在WTF Solidity内部标准系列教程中,我们将详细介绍Solidity合约中变量的存储布局,内存存储,以及calldata和returndata遵守的ABI编码规则,帮助大家理解Solidity的内部规则。这一讲,我们将介绍Solidity的值类型变量是如何在合约中存储的,你需要事先安装foundry。
EVM的账户存储(Account Storage)是一种映射(mapping,键值对存储),每个键和值都是256 bit的数据,它支持256 bit的读和写。这种存储在每个合约账户上都存在,并且是持久的:它的数据会保持在区块链上,直到被明确地修改。
对存储的读取(SLOAD)和写入(SSTORE)都需要gas,并且比内存操作更昂贵。这样设计可以防止滥用存储资源,因为所有的存储数据都需要在每个以太坊节点上保存。
在Solidity中,值类型(比如uint8和静态数组)和引用类型(比如映射和动态数组)的存储布局不同,这一讲我们只介绍前者。
合约中的第一个状态变量默认存储在槽0中,之后的状态变量(如果不能放在同一个存储槽)存储在槽1中,以此类推。在下面的ValueStorage1合约中,我们声明了2个uint256类型的状态变量:
contract ValueStorage1 {
uint256 public a = 5;
uint256 public b = 2;
}你可以使用下面的命令打印合约的存储布局,可以看到变量a被存储在Slot 0,变量b被存储在Slot 1。
forge inspect src/01_ValueStorage.sol:ValueStorage1 storage-layout --pretty| Name | Type | Slot | Offset | Bytes | Contract |
|---|---|---|---|---|---|
| a | uint256 | 0 | 0 | 32 | src/01_ValueStorage.sol:ValueStorage1 |
| b | uint256 | 1 | 0 | 32 | src/01_ValueStorage.sol:ValueStorage1 |
由于EVM的存储很贵,在合约中,状态变量以一种紧凑的方式存储:值类型只使用存储它们所需的字节数,如果多个连续状态变量的大小总和不足32字节,它们会被放在同一个存储槽中;如果一个值类型不适合一个存储槽的剩余部分,它将被存储在下一个存储槽。
在下面的ValueStorage2合约中,我们声明了4个状态变量。
contract ValueStorage2 {
uint128 public a = 5;
uint64 public b = 2;
uint32 public c = 3;
uint64 public d = 1;
}你可以使用下面的命令打印合约的存储布局:
forge inspect src/01_ValueStorage.sol:ValueStorage2 storage-layout --pretty我们可以看到,变量a,b和c都被存储在Slot 0中,这是因为它们的类型分别为uint128,uint64和uint32,而128+64+32=224小于256。变量d被存储在Slot 1,这是因为它是uint64类型,加上前面的变量就超过256位了。
| Name | Type | Slot | Offset | Bytes | Contract |
|---|---|---|---|---|---|
| a | uint128 | 0 | 0 | 16 | src/01_ValueStorage.sol:ValueStorage2 |
| b | uint64 | 0 | 16 | 8 | src/01_ValueStorage.sol:ValueStorage2 |
| c | uint32 | 0 | 24 | 4 | src/01_ValueStorage.sol:ValueStorage2 |
| d | uint64 | 1 | 0 | 8 | src/01_ValueStorage.sol:ValueStorage2 |
思考题:如果变量
d是uint32的话,它会被存储在哪里呢?
ValueStorage1合约中的变量a和b(均为uint256类型)会被存储为
a: 0x0000000000000000000000000000000000000000000000000000000000000005
b: 0x0000000000000000000000000000000000000000000000000000000000000002
ValueStorage2合约中的变量a,b和c(分别为uint128,uint64和uint32)会被存储为:
a b c: 0x0000000000000003000000000000000200000000000000000000000000000005
这是因为变量a是第一项,会从最右边开始存,值为5,占128位(16字节)。然后b从右数16字节开始存,值为2,占64位(8字节)。最后c从右数24字节开始存,值为3,占32位(4字节)。
在下面的ValueStorage3合约中,我们声明了一个S结构体,它包含两个成员:uint64类型的b和uint32类型的c。按照规则3的话,变量a和结构体s会共享Slot 0;而如果按照规则4,变量a会被单独保存在Slot 0,结构体s会被保存在Slot 1,而变量d会被保存在Slot 2。
contract ValueStorage3 {
struct S { uint64 b; uint32 c; }
uint128 public a = 5;
S public s = S(2, 3);
uint64 public d = 1;
}你可以使用下面的命令打印合约的存储布局:
forge inspect src/01_ValueStorage.sol:ValueStorage3 storage-layout --pretty可以看到,按照规则4,三个变量被保存在不同的存储槽中。
| Name | Type | Slot | Offset | Bytes | Contract |
|---|---|---|---|---|---|
| a | uint128 | 0 | 0 | 16 | src/01_ValueStorage.sol:ValueStorage3 |
| s | struct ValueStorage3.S | 1 | 0 | 32 | src/01_ValueStorage.sol:ValueStorage3 |
| d | uint64 | 2 | 0 | 8 | src/01_ValueStorage.sol:ValueStorage3 |
这一讲,我们介绍了值变量,静态数组和结构体数据在合约中的存储结构。
