Add slot derivation library (#4975)

This commit is contained in:
Hadrien Croubois
2024-03-27 22:17:46 +01:00
committed by GitHub
parent 5e3ba29b08
commit cb2aaaa04a
18 changed files with 777 additions and 63 deletions

View File

@ -0,0 +1,203 @@
// SPDX-License-Identifier: MIT
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.t.js.
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol";
contract SlotDerivationTest is Test {
using SlotDerivation for bytes32;
bytes[] private _array;
function testDeriveArray(uint256 length, uint256 offset) public {
length = bound(length, 1, type(uint256).max);
offset = bound(offset, 0, length - 1);
bytes32 baseSlot;
assembly {
baseSlot := _array.slot
sstore(baseSlot, length) // store length so solidity access does not revert
}
bytes storage derived = _array[offset];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveArray().offset(offset), derivedSlot);
}
mapping(address => bytes) private _addressMapping;
function testDeriveMappingAddress(address key) public {
bytes32 baseSlot;
assembly {
baseSlot := _addressMapping.slot
}
bytes storage derived = _addressMapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(bool => bytes) private _boolMapping;
function testDeriveMappingBoolean(bool key) public {
bytes32 baseSlot;
assembly {
baseSlot := _boolMapping.slot
}
bytes storage derived = _boolMapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(bytes32 => bytes) private _bytes32Mapping;
function testDeriveMappingBytes32(bytes32 key) public {
bytes32 baseSlot;
assembly {
baseSlot := _bytes32Mapping.slot
}
bytes storage derived = _bytes32Mapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(bytes4 => bytes) private _bytes4Mapping;
function testDeriveMappingBytes4(bytes4 key) public {
bytes32 baseSlot;
assembly {
baseSlot := _bytes4Mapping.slot
}
bytes storage derived = _bytes4Mapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(uint256 => bytes) private _uint256Mapping;
function testDeriveMappingUint256(uint256 key) public {
bytes32 baseSlot;
assembly {
baseSlot := _uint256Mapping.slot
}
bytes storage derived = _uint256Mapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(uint32 => bytes) private _uint32Mapping;
function testDeriveMappingUint32(uint32 key) public {
bytes32 baseSlot;
assembly {
baseSlot := _uint32Mapping.slot
}
bytes storage derived = _uint32Mapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(int256 => bytes) private _int256Mapping;
function testDeriveMappingInt256(int256 key) public {
bytes32 baseSlot;
assembly {
baseSlot := _int256Mapping.slot
}
bytes storage derived = _int256Mapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(int32 => bytes) private _int32Mapping;
function testDeriveMappingInt32(int32 key) public {
bytes32 baseSlot;
assembly {
baseSlot := _int32Mapping.slot
}
bytes storage derived = _int32Mapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(string => bytes) private _stringMapping;
function testDeriveMappingString(string memory key) public {
bytes32 baseSlot;
assembly {
baseSlot := _stringMapping.slot
}
bytes storage derived = _stringMapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
mapping(bytes => bytes) private _bytesMapping;
function testDeriveMappingBytes(bytes memory key) public {
bytes32 baseSlot;
assembly {
baseSlot := _bytesMapping.slot
}
bytes storage derived = _bytesMapping[key];
bytes32 derivedSlot;
assembly {
derivedSlot := derived.slot
}
assertEq(baseSlot.deriveMapping(key), derivedSlot);
}
}

View File

@ -0,0 +1,58 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { erc7201Slot } = require('../helpers/storage');
const { generators } = require('../helpers/random');
async function fixture() {
const [account] = await ethers.getSigners();
const mock = await ethers.deployContract('$SlotDerivation');
return { mock, account };
}
describe('SlotDerivation', function () {
beforeEach(async function () {
Object.assign(this, await loadFixture(fixture));
});
describe('namespaces', function () {
const namespace = 'example.main';
it('erc-7201', async function () {
expect(await this.mock.$erc7201Slot(namespace)).to.equal(erc7201Slot(namespace));
});
});
describe('derivation', function () {
it('offset', async function () {
const base = generators.bytes32();
const offset = generators.uint256();
expect(await this.mock.$offset(base, offset)).to.equal((ethers.toBigInt(base) + offset) & ethers.MaxUint256);
});
it('array', async function () {
const base = generators.bytes32();
expect(await this.mock.$deriveArray(base)).to.equal(ethers.keccak256(base));
});
describe('mapping', function () {
for (const { type, key, isValueType } of [
{ type: 'bool', key: true, isValueType: true },
{ type: 'address', key: generators.address(), isValueType: true },
{ type: 'bytes32', key: generators.bytes32(), isValueType: true },
{ type: 'uint256', key: generators.uint256(), isValueType: true },
{ type: 'int256', key: generators.int256(), isValueType: true },
{ type: 'bytes', key: generators.hexBytes(128), isValueType: false },
{ type: 'string', key: 'lorem ipsum', isValueType: false },
]) {
it(type, async function () {
const base = generators.bytes32();
const expected = isValueType
? ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode([type, 'bytes32'], [key, base]))
: ethers.solidityPackedKeccak256([type, 'bytes32'], [key, base]);
expect(await this.mock[`$deriveMapping(bytes32,${type})`](base, key)).to.equal(expected);
});
}
});
});
});

View File

@ -19,10 +19,12 @@ describe('StorageSlot', function () {
for (const { type, value, zero } of [
{ type: 'Boolean', value: true, zero: false },
{ type: 'Address', value: generators.address(), zero: ethers.ZeroAddress },
{ type: 'Bytes32', value: generators.bytes32(), zero: ethers.ZeroHash },
{ type: 'Address', value: generators.address(), zero: generators.address.zero },
{ type: 'Bytes32', value: generators.bytes32(), zero: generators.bytes32.zero },
{ type: 'Uint256', value: generators.uint256(), zero: generators.uint256.zero },
{ type: 'Int256', value: generators.int256(), zero: generators.int256.zero },
{ type: 'Bytes', value: generators.hexBytes(128), zero: generators.hexBytes.zero },
{ type: 'String', value: 'lorem ipsum', zero: '' },
{ type: 'Bytes', value: generators.hexBytes(128), zero: '0x' },
]) {
describe(`${type} storage slot`, function () {
it('set', async function () {