Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add erc1967 and erc7201 slot derivation + fuzzing
  • Loading branch information
Amxx committed Mar 18, 2024
commit 0e8db95c4c1756c1f80f7d09397fe0219a56ab98
8 changes: 8 additions & 0 deletions contracts/mocks/SlotsMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import {Slots} from "../utils/Slots.sol";
contract SlotsMock is Multicall {
using Slots for *;

function erc1967slot(string memory path) public pure returns (bytes32 slot) {
return path.erc1967slot();
}

function erc7201slot(string memory path) public pure returns (bytes32 slot) {
return path.erc7201slot();
}

event BoolSlotValue(bytes32 slot, bool value);

function tloadBoolSlot(bytes32 slot) public {
Expand Down
21 changes: 21 additions & 0 deletions contracts/utils/Slots.sol
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ernestognw @frangio Like this new approach?

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ pragma solidity ^0.8.24;
* Note: Transient storage operations (`tload` and `tstore`) only works on networks where EIP-1153[https://eips.ethereum.org/EIPS/eip-1153] is available.
*/
library Slots {
/**
* @dev Derivate an ERC-1967 slot from a string (path).
*/
function erc1967slot(string memory path) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
slot := sub(keccak256(add(path, 0x20), mload(path)), 1)
}
}

/**
* @dev Derivate an ERC-7201 slot from a string (path).
*/
function erc7201slot(string memory path) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, sub(keccak256(add(path, 0x20), mload(path)), 1))
slot := and(keccak256(0x00, 0x20), not(0xff))
}
}

/**
* @dev Add an offset to a slot to get the n-th element of a structure or an array.
*/
Expand Down
21 changes: 21 additions & 0 deletions scripts/generate/templates/Slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ pragma solidity ^0.8.24;
`;

const tooling = () => `\
/**
* @dev Derivate an ERC-1967 slot from a string (path).
*/
function erc1967slot(string memory path) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
slot := sub(keccak256(add(path, 0x20), mload(path)), 1)
}
}

/**
* @dev Derivate an ERC-7201 slot from a string (path).
*/
function erc7201slot(string memory path) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, sub(keccak256(add(path, 0x20), mload(path)), 1))
slot := and(keccak256(0x00, 0x20), not(0xff))
}
}

/**
* @dev Add an offset to a slot to get the n-th element of a structure or an array.
*/
Expand Down
15 changes: 13 additions & 2 deletions scripts/generate/templates/SlotsMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ import {Multicall} from "../utils/Multicall.sol";
import {Slots} from "../utils/Slots.sol";
`;

const common = () => `\
using Slots for *;

function erc1967slot(string memory path) public pure returns (bytes32 slot) {
return path.erc1967slot();
}

function erc7201slot(string memory path) public pure returns (bytes32 slot) {
return path.erc7201slot();
}
`;

const generate = ({ udvt, type }) => `\
event ${udvt}Value(bytes32 slot, ${type} value);

Expand All @@ -24,8 +36,7 @@ function tstore(bytes32 slot, ${type} value) public {
module.exports = format(
header.trimEnd(),
'contract SlotsMock is Multicall {',
'using Slots for *;',
'',
common(),
TYPES.flatMap(t => generate(t)),
'}',
);
78 changes: 78 additions & 0 deletions test/utils/Slots.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";

import {Slots} from "@openzeppelin/contracts/utils/Slots.sol";

contract SlotsTest is Test {
using Slots for *;

// Variable declarations
uint256 private _variable;
uint256[] private _array;
mapping(address => uint256) private _mapping;

// Tests
function testValue1(uint256 value) public {
// set in solidity
_variable = value;
// read using Slots
assertEq(_getVariableSlot().asUint256Slot().sload(), value);
}

function testValue2(uint256 value) public {
// set using Slots
_getVariableSlot().asUint256Slot().sstore(value);
// read in solidity
assertEq(_variable, value);
}

function testArray1(uint256[] calldata values) public {
// set in solidity
_array = values;
// read using Slots
assertEq(_getArraySlot().asUint256Slot().sload(), values.length);
for (uint256 i = 0; i < values.length; ++i) {
assertEq(_getArraySlot().derivateArray().offset(i).asUint256Slot().sload(), values[i]);
}
}

function testArray2(uint256[] calldata values) public {
// set using Slots
_getArraySlot().asUint256Slot().sstore(values.length);
for (uint256 i = 0; i < values.length; ++i) {
_getArraySlot().derivateArray().offset(i).asUint256Slot().sstore(values[i]);
}
// read in solidity
assertEq(_array, values);
}

function testMapping1(address key, uint256 value) public {
// set in solidity
_mapping[key] = value;
// read using Slots
assertEq(_getMappingSlot().derivateMapping(key).asUint256Slot().sload(), value);
}

function testMapping2(address key, uint256 value) public {
// set using Slots
_getMappingSlot().derivateMapping(key).asUint256Slot().sstore(value);
// read in solidity
assertEq(_mapping[key], value);
}

// Slot extraction
function _getVariableSlot() public pure returns (bytes32 slot) {
assembly { slot := _variable.slot }
}

function _getArraySlot() public pure returns (bytes32 slot) {
assembly { slot := _array.slot }
}

function _getMappingSlot() public pure returns (bytes32 slot) {
assembly { slot := _mapping.slot }
}
}
13 changes: 13 additions & 0 deletions test/utils/Slots.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { erc1967slot, erc7201slot } = require('../helpers/storage');
const { generators } = require('../helpers/random');
const { TYPES } = require('../../scripts/generate/templates/Slots.opts');

Expand All @@ -16,6 +17,18 @@ describe('Slots', function () {
Object.assign(this, await loadFixture(fixture));
});

describe('slot derivation', function () {
const path = 'example.main';

it('erc-1967', async function () {
expect(await this.mock.erc1967slot(path)).to.equal(erc1967slot(path));
});

it('erc-7201', async function () {
expect(await this.mock.erc7201slot(path)).to.equal(erc7201slot(path));
});
});

for (const { type, value, zero } of [
{ type: 'bool', value: true, zero: false },
{ type: 'address', value: generators.address(), zero: ethers.ZeroAddress },
Expand Down