数组与映射是如何存放在“存储槽”的?
对于固定长度的数组,处理方式相对简单。
contract StorageComplex {
uint256[3] fixedArray;
constructor() {
fixedArray = [99, 999, 9999];
}
function fixedArrayView(uint256 index) external view returns (uint256 ret) {
assembly {
ret := sload(add(fixedArray.slot, index))
}
}
}
uint256[3] fixedArray;
就相当于声明了三个 uint256
类型的变量,这些变量依次占据从槽 0 开始的存储位置。
动态长度的数组处理起来稍微复杂一些。
contract StorageBasics {
uint256[] bigArray;
uint8[] smallArray;
constructor() {
bigArray = [10, 20, 30];
smallArray = [1, 2, 3];
}
function bigArrayLength() external view returns (uint256 ret) {
assembly {
ret := sload(bigArray.slot)
}
} // returns 3
}
可以看到槽 0 存放的数据是 3, 表示数组 bigArray 的当前长度。那么,数组中的元素存放在哪里呢?
计算方法为:keccak256(slot) + index
, 即“存放长度的槽的哈希”就是开始存放元素的存储槽的位置,然后从这个位置开始依次存放元素。
function readBigArrayLocation(uint256 index) external view returns (uint256 ret) {
uint256 slot;
assembly {
slot := bigArray.slot
}
bytes32 location = keccak256(abi.encode(slot));
assembly {
ret := sload(add(location, index))
}
}
但是,对于非 uint256 类型的动态数组,几个元素会共用一个槽。
function smallArrayLength() external view returns (uint256 ret) {
assembly {
ret := sload(smallArray.slot)
}
} // returns 3
function readSmallArrayLocation(uint256 index) external view returns (bytes32 ret) {
uint256 slot;
assembly {
slot := smallArray.slot
}
bytes32 location = keccak256(abi.encode(slot));
assembly {
ret := sload(add(location, index))
}
} // input 0 returns 0x0000000000000000000000000000000000000000000000000000000000030201
映射的存储方式与动态数组相似。不同之处在于,动态数组存储槽的位置是 keccak256(slot)
,然后加上索引;而映射则是将 slot
和 key
连接后再做哈希处理,即 keccak256(key, slot)
。
contract StorageComplex {
mapping(uint256 => uint256) public myMapping;
constructor() {
myMapping[10] = 5;
myMapping[11] = 6;
}
function getMapping(uint256 key) external view returns (uint256 ret) {
uint256 slot;
assembly {
slot := myMapping.slot
}
bytes32 location = keccak256(abi....
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!