Solidity中数据的布局
1.Storage中
1.1-基本原则(基本的值类型)
在这里面的变量都是独立的,互相不影响,所以非常"安全";
均存储在slot中,slot有2^256个,每个长度256位;
从0开始连续往后存储(除动态数组和映射),当有连续几个都小于256位时,会尝试将它们放在同一个slot中;
同时也遵循以下规则:
- 比如有小于256位的数据需要存储,会存在低阶位置(右边)
Value types使用同样多的位数去存储(比如uint128,会使用128位去存储它,无论它何值)- 当前
slot不够存,则会存在下一个slot中 Structs和Array会在一个新的slot开始存,并且它们的值会打包的很紧密Struct和Array后面的数据,会单开一个slot
1.2-Dynamic Arrays & mapping
由于动态数组和映射的不可预测性,它们不像正常的状态变量一样紧跟着在后面存,而是占用一个新的slot,再根据它们自己的规则去存;
假设有一个动态数组或者映射,它占用的是slot的第p号位置;
对于动态数组而言:
在slot p的位置存储着数组长度,在slot Keccak256(p)的位置开始存储数组元素;
| Key | Value |
|---|---|
slot 0 |
其他数据 |
| ... | ... |
slot p |
数组长度 |
| ... | ... |
slot Keccak256(p) |
元素1 |
slot Keccak256(p) + 1 |
元素2 |
| ... | ... |
function getHash() public pure returns (bytes memory, bytes32) {
return (abi.encodePacked(uint256(0)), keccak256(abi.encodePacked(uint256(0))));
// bytes: 0x0000000000000000000000000000000000000000000000000000000000000000
// bytes32: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
}
function getHash() public pure returns (bytes memory, bytes32) {
return (abi.encodePacked(uint256(1)), keccak256(abi.encodePacked(uint256(1))));
// bytes: 0x0000000000000000000000000000000000000000000000000000000000000001
// bytes32: 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
}

同样的,若是每个元素数据小于256位,和上面的规则一样,紧凑的往下写,从小端往大端写:

对于映射而言:
在slot p的位置会存储全0数据,代表着此处是一个映射的开始;
映射的key会存在一个经过计算的slot:slot Keccak256(h(k) . p),映射的value则会被直接存在对应slot的value中;
其中,h(k)中的h代表类型,address、uint256等等,k就是映射的key值;
.代表连接,solidity就可以用abi.encode(a, b)来实现相同功能;
p是当前映射首个元素的slot编号;
| Key | Value |
|---|---|
slot 0 |
其他数据 |
| ... | ... |
slot p |
全0(占用,代表这边是个映射的起始) |
| ... | ... |
slot Keccak256(h(key1) . p) |
key1.value |
| ... | ... |
slot Keccak256(h(key2) . p) |
key2.value |
| ... | ... |
例子1(多个连续映射)
两个映射,分别存储以下数据:
contract Exp {
uint256 public a = 0x1;
mapping (address => uint256) public b;
mapping (address => uint256) public c;
function addMapping() public {
b[address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4)] = 0xb11;
c[address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB)] = 0xc11;
b[address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)] = 0xb22;
c[address(0x617F2E2fD72FD9D5503197092aC168c91465E7f2)] = 0xc22;
b[address(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db)] = 0xb33;
c[address(0x17F6AD8Ef982297579C203069C1DbfFE4348c372)] = 0xc33;
}
}
这是该合约调试的结果:
- 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
- key:0x0000000000000000000000000000000000000000000000000000000000000000
- value:0x01
- 0x34a2b38493519efd2aea7c8727c9ed8774c96c96418d940632b22aa9df022106:Object
- key:0x36306db541fd1551fd93a60031e8a8c89d69ddef41d6249f5fdc265dbc8fffa2
- value:0x0000000000000000000000000000000000000000000000000000000000000b11
- 0xf5e7424a6e4079071264af0bf2d5ca3530102dfccdbd193e7d54d0d4400332e0:Object
- key:0x4f3049662ef87b9e630d98ff73343a6afe9cd9c07fbd5dfe02b76d8fef856cb2
- value:0x0000000000000000000000000000000000000000000000000000000000000c11
- 0x94b29c01ed483e694a7ecf386d384987d4d3e9d4e6c476f5b97302b23ff871c9:Object
- key:0x9d4d959825f0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
- value:0x0000000000000000000000000000000000000000000000000000000000000b22
- 0x3a885bd4536bf506a6b539c1ecc761dabfeafcbdc0e1f368493815c2bebe5da6:Object
- key:0xd9ea8f4d7f096f77b3e4169a2c25f1eb750f25bc40cad854f045cfb680dbd21d
- value:0x0000000000000000000000000000000000000000000000000000000000000c22
- 0x709c4b690342f2b3b4553bcc137e89412180007fbe4ac36592d4bb2c9003b39b:Object
- key:0xe20f19dc6931eb9e42fe3f21abe1a9ef59942d8e586871d88564d0d0b63a5e5c
- value:0x0000000000000000000000000000000000000000000000000000000000000b33
- 0x16086c92369a425dfd790c80c90e5c1364cccb1a6ffc51553bc237bad3e37c09:Object
- key:0xeb070bdf6b9b9e994516da4d935f3719e4f3890dd106aba68bfe82094e359b19
- value:0x0000000000000000000000000000000000000000000000000000000000000c33
下面我们来验证,按照推断,映射b应该从slot 1开始,映射c应该从slot 2开始;
// 映射b的第一个元素,bytes32: 0x36306db541fd1551fd93a60031e8a8c89d69ddef41d6249f5fdc265dbc8fffa2
function getHash_b1() public pure returns (bytes32){
return keccak256(abi.encode(address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), uint256(1)));
}
// 映射b的第二个元素,bytes32: 0x9d4d959825f0680278e64197773b2a50cd78b2b2cb00711ddbeebf0bf93cd8a4
function getHash_b2() public pure returns (bytes32){
return keccak256((abi.encode(address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2), uint256(1))));
}
// 映射c的第一个元素,bytes32: 0x4f3049662ef87b9e630d98ff73343a6afe9cd9c07fbd5dfe02b76d8fef856cb2
function getHash_c1() public pure returns (bytes32){
return keccak256((abi.encode(address(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB), uint256(2))));
}
// 映射c的第二个元素,bytes32: 0xd9ea8f4d7f096f77b3e4169a2c25f1eb750f25bc40cad854f045cfb680dbd21d
function getHash_c2() public pure returns (bytes32){
return keccak256((abi.encode(address(0x617F2E2fD72FD9D5503197092aC168c91465E7f2), uint256(2))));
}
例子2(多重映射)
一个三重映射:address => uint256 => uint256:
contract Exp {
uint256 public a = 0x1;
mapping (address => mapping (uint256 => uint256)) public data;
function addMapping() public {
data[address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4)][3] = 4;
data[address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)][7] = 8;
}
}
调试的结果:
- 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
- key:0x0000000000000000000000000000000000000000000000000000000000000000
- value:0x01
- 0x6d56a438fa5dd6d0bbc7c165fa370e93f31dc10c1348391ce17cdc6a22c94479:Object
- key:0xf72ff45ebf51ff9fe6584c33c990ae8cc8de482bd70289044a93604d667fc9ad
- value:0x0000000000000000000000000000000000000000000000000000000000000004
- 0xc116603da12a240205c7c8b682cead30acae1a1f283e7a40647ef98139f92d02:Object
- key:0x287bec35ef93239ef6c83981c0922f4e9288d308bc2bfa6a0d60780af18028b6
- value:0x0000000000000000000000000000000000000000000000000000000000000008
验证,从前往后一个一个算,Keccak256(address(k) . p) --> Keccak256(uint256(k) . Keccak256(address(k) . p)):
// 映射的第一个元素:bytes32: 0xf72ff45ebf51ff9fe6584c33c990ae8cc8de482bd70289044a93604d667fc9ad
function getHash_data1() public pure returns (bytes32){
// 先计算[address(k), p]
bytes32 temp = keccak256(abi.encode(address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), uint256(1)));
// 再计算{uint256(k), [address(k), p]}
return keccak256(abi.encode(uint256(3), temp));
}
// 映射的第二个元素:bytes32: 0x287bec35ef93239ef6c83981c0922f4e9288d308bc2bfa6a0d60780af18028b6
function getHash_data2() public pure returns (bytes32){
bytes32 temp = keccak256(abi.encode(address(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2), uint256(1)));
return keccak256(abi.encode(uint256(7), temp));
}
例子3(string和bytes)
注意:
对于基本值类型,
h都是将其扩充为256位;对于
string和byte array,h不将其扩充,保持原样;
示例合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// import "./Ethernaut.sol";
contract Exp {
uint256 public a = 0x1;
mapping (string => uint256) public data;
// key:0x3760e61363bd6582908f2b38ae298a6e2239ed14711faf5dace0ac9362a82e59
// value:0x0000000000000000000000000000000000000000000000000000000000000002
function addMapping() public {
data["WZM"] = 2;
}
// 0:bytes: a 0x575a4d
// 1:bytes: b 0x0000000000000000000000000000000000000000000000000000000000000001
// 2:bytes: c 0x575a4d0000000000000000000000000000000000000000000000000000000000000001
function getbytes() public pure returns (bytes memory a, bytes memory b, bytes memory c){
a = (abi.encodePacked("WZM"));
b = (abi.encodePacked(uint256(1)));
c = abi.encodePacked("WZM", uint256(1));
}
// bytes32: 0x3760e61363bd6582908f2b38ae298a6e2239ed14711faf5dace0ac9362a82e59
function getHash() public pure returns (bytes32){
return keccak256(abi.encodePacked("WZM", uint256(1)));
}
}
1.3-string & bytes
这两个类型编码的方式相同,就和bytes1[]类似,但不同长度的编码方式不一样;
长度不超过31字节
新起一个slot,编号不需要计算,前31个字节存放数据,最后一个字节存放数据长度 * 2(也就是有多少个hex);
例子:
contract Exp {
uint256 public a = 0x1;
bytes public b = "WZM";
string public c = "WZM1230";
}
结果:
- 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
- key:0x0000000000000000000000000000000000000000000000000000000000000000
- value:0x0000000000000000000000000000000000000000000000000000000000000001
- 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6:Object
- key:0x0000000000000000000000000000000000000000000000000000000000000001
- value:0x575a4d0000000000000000000000000000000000000000000000000000000006
- 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace:Object
- key:0x0000000000000000000000000000000000000000000000000000000000000002
- value:0x575a4d313233300000000000000000000000000000000000000000000000000e
长度超过31字节
和动态数组几乎一样,在slot p的位置存储着长度length * 2 + 1,在slot Keccak256(p)的位置开始存储数组元素;
例子:
contract Exp {
uint256 public a = 0x1;
// length = 33
bytes public b = "WZMWZMWZMWZMWZMWZMWZMWZMWZMWZMWZM";
// length = 35
string public c = "WZM1230WZM1230WZM1230WZM1230WZM1230";
}
结果:
- 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563:Object
- key:0x0000000000000000000000000000000000000000000000000000000000000000
- value:0x0000000000000000000000000000000000000000000000000000000000000001
- 0xb5d9d894133a730aa651ef62d26b0ffa846233c74177a591a4a896adfda97d22:Object----2.
slot Keccak(1),存储数据,和bytes1[]一样的存储方式
- key:0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
- value:0x575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a4d575a
- 0xea7809e925a8989e20c901c4c1da82f0ba29b26797760d445a0ce4cf3c6fbd31:Object----3.
slot Keccak(1)+1,继续存储数据
- key:0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7
- value:0x4d00000000000000000000000000000000000000000000000000000000000000
- 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6:Object----1.
slot 1,存储长度(length * 2 + 1)
- key:0x0000000000000000000000000000000000000000000000000000000000000001
- value:0x0000000000000000000000000000000000000000000000000000000000000043
- 0x1ab0c6948a275349ae45a06aad66a8bd65ac18074615d53676c09b67809099e0:Object
- key:0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace
- value:0x575a4d31323330575a4d31323330575a4d31323330575a4d31323330575a4d31
- 0x2f2149d90beac0570c7f26368e4bc897ca24bba51b1a0f4960d358f764f11f31:Object
- key:0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf
- value:0x3233300000000000000000000000000000000000000000000000000000000000
- 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace:Object
- key:0x0000000000000000000000000000000000000000000000000000000000000002
- value:0x0000000000000000000000000000000000000000000000000000000000000047
2.Memory中
仅有
string,bytes,array,struct使用memory;
以太坊内存中保留了前四个slot,分别是:
0x00 - 0x1f, 0x20 - 0x3f:两个slot,存储散列方法0x40 - 0x5f:当前分配的内存大小0x60 - 0x7f:zero slot(此部分不能被写入)
所以在分析字节码的时候,经常会看到代码的一开始有
MEM[xxx] = 0x80:
每个元素均占用一个slot,这点和在Storage中不同:
uint8[4] a;
在Storage中,仅占用1个slot;而在Memory中,却占用4个slot;
例子:
contract Exp {
struct Wallet{
address addr;
uint16 num;
}
function Demo() public {
bytes memory a = "123";
bytes8[] memory b = new bytes8[](0x3);
string memory c = "WZM1230";
Wallet memory wallet = Wallet(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db, 0x123);
}
}




浙公网安备 33010602011771号