从foundry工程化的角度详细解读Openzeppelin中的StorageSlot库及对应测试。
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/utils/StorageSlot.sol
StorageSlot库是用于向storage slots中读和写基本类型数据。使用可升级合约时往往会使用该库用于避免storage slot冲突。
该库在不需要使用内联汇编的情况下,可以对指定的slot进行读写。具体slot中存储的值即StorageSlot库中定义的各结构体中的value值。
注:一个合约中的slot号就是一个bytes32变量。所以只要指定一个bytes32的值就可以以其作为slot号进行对应slot的值读写。
封装StorageSlot library成为一个可调用合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/utils/StorageSlot.sol";
contract MockStorageSlot {
using StorageSlot for bytes32;
bytes32 public constant _ADDRESS_SLOT = keccak256("address slot");
bytes32 public constant _BOOLEAN_SLOT = keccak256("boolean slot");
bytes32 public constant _BYTES32_SLOT = keccak256("bytes32 slot");
bytes32 public constant _UINT256_SLOT = keccak256("uint256 slot");
uint public slot0 = 0;
uint public slot1 = 10;
uint public slot2 = 20;
function setAddressSlot(address newAddr) external {
_ADDRESS_SLOT.getAddressSlot().value = newAddr;
}
function getAddressSlot() external view returns (address){
return _ADDRESS_SLOT.getAddressSlot().value;
}
function setBooleanSlot(bool newBool) external {
_BOOLEAN_SLOT.getBooleanSlot().value = newBool;
}
function getBooleanSlot() external view returns (bool){
return _BOOLEAN_SLOT.getBooleanSlot().value;
}
function setBytes32Slot(bytes32 newBytes32) external {
_BYTES32_SLOT.getBytes32Slot().value = newBytes32;
}
function getBytes32Slot() external view returns (bytes32){
return _BYTES32_SLOT.getBytes32Slot().value;
}
function setUint256Slot(uint newUint) external {
_UINT256_SLOT.getUint256Slot().value = newUint;
}
function getUint256Slot() external view returns (uint){
return _UINT256_SLOT.getUint256Slot().value;
}
function setUintValueBySlotNumber(bytes32 implementation, uint newUint) external {
implementation.getUint256Slot().value = newUint;
}
function getUintValueBySlotNumber(bytes32 implementation) external view returns (uint){
return implementation.getUint256Slot().value;
}
}
全部foundry测试合约:
通过定义四种结构体来分别在slot中存储address/bool/bytes32/uint256类型的基本类型变量:
struct AddressSlot {
// slot中存储的address类型的变量值
address value;
}
struct BooleanSlot {
// slot中存储的bool类型的变量值
bool value;
}
struct Bytes32Slot {
// slot中存储的bytes32类型的变量值
bytes32 value;
}
struct Uint256Slot {
// slot中存储的uint256类型的变量值
uint256 value;
}
传入slot号,返回指向该slot的AddressSlot结构体的storage指针:
代码解读
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
// 设置storage变量指针指向传入的slot号位置
r.slot := slot
}
}
foundry代码验证
contract StorageSlotTest is Test {
MockStorageSlot testing = new MockStorageSlot();
function test_SetAndGetAddressSlot() external {
assertEq(testing.getAddressSlot(), address(0));
testing.setAddressSlot(address(1024));
assertEq(testing.getAddressSlot(), address(1024));
// check the slot value by the slot number
bytes32 slotNumber = testing._ADDRESS_SLOT();
address valueInSlot = address(uint160(uint(vm.load(address(testing), slotNumber))));
assertEq(testing.getAddressSlot(), valueInSlot);
}
}
传入slot号,返回指向该slot的BooleanSlot结构体的storage指针:
代码解读
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
// 设置storage变量指针指向传入的slot号位置
assembly {
r.slot := slot
}
}
foundry代码验证
contract StorageSlotTest is Test {
MockStorageSlot testing = new MockStorageSlot();
function test_SetAndGetBooleanSlot() external {
assertFalse(testing.getBooleanSlot());
testing.setBooleanSlot(true);
assertTrue(testing.getBooleanSlot());
// check the slot value by the slot number
bytes32 slotNumber = testing._BOOLEAN_SLOT();
bool valueInSlot = vm.load(address(testing), slotNumber) == bytes32(0) ? false : true;
assertEq(testing.getBooleanSlot(), valueInSlot);
}
}
传入slot号,返回指向该slot的Bytes32Slot结构体的storage指针:
代码解读
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
// 设置storage变量指针指向传入的slot号位置
assembly {
r.slot := slot
}
}
foundry代码验证
contract StorageSlotTest is Test {
MockStorageSlot testing = new MockStorageSlot();
function test_SetAndGetBytes32Slot() external {
assertEq(testing.getBytes32Slot(), "");
testing.setBytes32Slot("a");
assertEq(testing.getBytes32Slot(), "a");
// check the slot value by the slot number
bytes32 slotNumber = testing._BYTES32_SLOT();
bytes32 valueInSlot = vm.load(address(testing), slotNumber);
assertEq(testing.getBytes32Slot(), valueInSlot);
}
}
传入slot号,返回指向该slot的Uint256Slot结构体的storage指针:
代码解读
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
// 设置storage变量指针指向传入的slot号位置
assembly {
r.slot := slot
}
}
foundry代码验证
contract StorageSlotTest is Test {
MockStorageSlot testing = new MockStorageSlot();
function test_SetAndGetUint256Slot() external {
assertEq(testing.getUint256Slot(), 0);
testing.setUint256Slot(1024);
assertEq(testing.getUint256Slot(), 1024);
// check the slot value by the slot number
bytes32 slotNumber = testing._UINT256_SLOT();
uint valueInSlot = uint(vm.load(address(testing), slotNumber));
assertEq(testing.getUint256Slot(), valueInSlot);
}
function test_SetAndGetUintSlotForTheDefaultStorageVariable() external {
assertEq(testing.slot0(), 0);
// set the slot0 by slot number
testing.setUintValueBySlotNumber(bytes32(0), 1024);
// check the query by testing.slot0() and slot number
assertEq(testing.getUintValueBySlotNumber(bytes32(0)), 1024);
assertEq(testing.getUintValueBySlotNumber(bytes32(0)), testing.slot0());
// check the value of slot1 and slot2
assertEq(testing.slot1(), 10);
assertEq(testing.slot1(), testing.getUintValueBySlotNumber(bytes32(uint(1))));
assertEq(testing.slot2(), 20);
assertEq(testing.slot2(), testing.getUintValueBySlotNumber(bytes32(uint(2))));
}
}
ps:\ 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!