Skip to content
5 changes: 5 additions & 0 deletions .changeset/gorgeous-badgers-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`SlotDerivation`: Add a library of methods for derivating common storage slots.
8 changes: 8 additions & 0 deletions contracts/mocks/StorageSlotMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ contract StorageSlotMock {
slot.getUint256Slot().value = value;
}

function setInt256Slot(bytes32 slot, int256 value) public {
slot.getInt256Slot().value = value;
}

function getBooleanSlot(bytes32 slot) public view returns (bool) {
return slot.getBooleanSlot().value;
}
Expand All @@ -39,6 +43,10 @@ contract StorageSlotMock {
return slot.getUint256Slot().value;
}

function getInt256Slot(bytes32 slot) public view returns (int256) {
return slot.getInt256Slot().value;
}

mapping(uint256 key => string) public stringMap;

function setStringSlot(bytes32 slot, string calldata value) public {
Expand Down
26 changes: 8 additions & 18 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

pragma solidity ^0.8.20;

import {SlotDerivation} from "./SlotDerivation.sol";
import {StorageSlot} from "./StorageSlot.sol";
import {Math} from "./math/Math.sol";

/**
* @dev Collection of functions related to array types.
*/
library Arrays {
using SlotDerivation for bytes32;
using StorageSlot for bytes32;

/**
Expand Down Expand Up @@ -361,15 +363,11 @@ library Arrays {
*/
function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
bytes32 slot;
// We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr`
// following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays.

/// @solidity memory-safe-assembly
assembly {
mstore(0, arr.slot)
slot := add(keccak256(0, 0x20), pos)
slot := arr.slot
}
return slot.getAddressSlot();
return slot.deriveArray().offset(pos).getAddressSlot();
}

/**
Expand All @@ -379,15 +377,11 @@ library Arrays {
*/
function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
bytes32 slot;
// We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr`
// following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays.

/// @solidity memory-safe-assembly
assembly {
mstore(0, arr.slot)
slot := add(keccak256(0, 0x20), pos)
slot := arr.slot
}
return slot.getBytes32Slot();
return slot.deriveArray().offset(pos).getBytes32Slot();
}

/**
Expand All @@ -397,15 +391,11 @@ library Arrays {
*/
function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
bytes32 slot;
// We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr`
// following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays.

/// @solidity memory-safe-assembly
assembly {
mstore(0, arr.slot)
slot := add(keccak256(0, 0x20), pos)
slot := arr.slot
}
return slot.getUint256Slot();
return slot.deriveArray().offset(pos).getUint256Slot();
}

/**
Expand Down
146 changes: 146 additions & 0 deletions contracts/utils/SlotDerivation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.

pragma solidity ^0.8.20;

/**
* @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
* corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
* the solidity language / compiler.
*
* See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
*/
library SlotDerivation {
/**
* @dev Derive an ERC-1967 slot from a string (namespace).
*/
function erc1967slot(string memory namespace) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
slot := sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)
}
}

/**
* @dev Derive an ERC-7201 slot from a string (namespace).
*/
function erc7201slot(string memory namespace) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 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.
*/
function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
unchecked {
return bytes32(uint256(slot) + pos);
}
}

/**
* @dev Derive the location of the first element in an array from the slot where the length is stored.
*/
function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, slot)
result := keccak256(0x00, 0x20)
}
}

/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}

/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}

/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}

/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}

/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}

/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}

/**
* @dev Derive the location of a mapping element from the key.
*/
function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}
}
14 changes: 14 additions & 0 deletions contracts/utils/StorageSlot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ library StorageSlot {
uint256 value;
}

struct Int256Slot {
int256 value;
}

struct StringSlot {
string value;
}
Expand Down Expand Up @@ -93,6 +97,16 @@ library StorageSlot {
}
}

/**
* @dev Returns an `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}

/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
Expand Down
2 changes: 2 additions & 0 deletions scripts/generate/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ for (const [file, template] of Object.entries({
'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js',
'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js',
'utils/structs/Checkpoints.sol': './templates/Checkpoints.js',
'utils/SlotDerivation.sol': './templates/SlotDerivation.js',
'utils/StorageSlot.sol': './templates/StorageSlot.js',
})) {
generateFromTemplate(file, template, './contracts/');
Expand All @@ -44,6 +45,7 @@ for (const [file, template] of Object.entries({
// Tests
for (const [file, template] of Object.entries({
'utils/structs/Checkpoints.t.sol': './templates/Checkpoints.t.js',
'utils/SlotDerivation.t.sol': './templates/SlotDerivation.t.js',
})) {
generateFromTemplate(file, template, './test/');
}
13 changes: 13 additions & 0 deletions scripts/generate/templates/Slot.opts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { capitalize } = require('../../helpers');

const TYPES = [
{ type: 'address', isValueType: true },
{ type: 'bool', isValueType: true, name: 'Boolean' },
{ type: 'bytes32', isValueType: true, variants: ['bytes4'] },
{ type: 'uint256', isValueType: true, variants: ['uint32'] },
{ type: 'int256', isValueType: true, variants: ['int32'] },
{ type: 'string', isValueType: false },
{ type: 'bytes', isValueType: false },
].map(type => Object.assign(type, { name: type.name ?? capitalize(type.type) }));

module.exports = { TYPES };
Loading