Skip to content
Prev Previous commit
Next Next commit
Apply PR suggestions
  • Loading branch information
ernestognw committed Mar 27, 2024
commit d691ec9cc178edcf37b8e9499a3d64b4c67f1903
21 changes: 19 additions & 2 deletions contracts/utils/SlotDerivation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@ pragma solidity ^0.8.20;
* 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.].
*
* Example usage:
* ```solidity
* contract Example {
* // Add the library methods
* using SlotDerivation for bytes32;
*
* // Declare a namespace
* string private constant _NAMESPACE = "<namespace>" // eg. OpenZeppelin.Slot
*
* function storagePointer() internal view returns (bytes32) {
* return _NAMESPACE.erc7201Slot(); // or erc1967Slot()
* }
* }
* ```
*
* TIP: Consider using this library along with {StorageSlot}.
*/
library SlotDerivation {
/**
* @dev Derive an ERC-1967 slot from a string (namespace).
*/
function erc1967slot(string memory namespace) internal pure returns (bytes32 slot) {
function erc1967Slot(string memory namespace) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
slot := sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)
Expand All @@ -24,7 +41,7 @@ library SlotDerivation {
/**
* @dev Derive an ERC-7201 slot from a string (namespace).
*/
function erc7201slot(string memory namespace) internal pure returns (bytes32 slot) {
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))
Expand Down
3 changes: 3 additions & 0 deletions contracts/utils/StorageSlot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pragma solidity ^0.8.20;
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
Expand All @@ -27,6 +28,8 @@ pragma solidity ^0.8.20;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
Expand Down
21 changes: 19 additions & 2 deletions scripts/generate/templates/SlotDerivation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,31 @@ pragma solidity ^0.8.20;
* 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.].
*
* Example usage:
* \`\`\`solidity
* contract Example {
* // Add the library methods
* using SlotDerivation for bytes32;
*
* // Declare a namespace
* string private constant _NAMESPACE = "<namespace>" // eg. OpenZeppelin.Slot
*
* function storagePointer() internal view returns (bytes32) {
* return _NAMESPACE.erc7201Slot(); // or erc1967Slot()
* }
* }
* \`\`\`
*
* TIP: Consider using this library along with {StorageSlot}.
*/
`;

const namespace = `\
/**
* @dev Derive an ERC-1967 slot from a string (namespace).
*/
function erc1967slot(string memory namespace) internal pure returns (bytes32 slot) {
function erc1967Slot(string memory namespace) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
slot := sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)
Expand All @@ -27,7 +44,7 @@ function erc1967slot(string memory namespace) internal pure returns (bytes32 slo
/**
* @dev Derive an ERC-7201 slot from a string (namespace).
*/
function erc7201slot(string memory namespace) internal pure returns (bytes32 slot) {
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))
Expand Down
3 changes: 3 additions & 0 deletions scripts/generate/templates/StorageSlot.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pragma solidity ^0.8.20;
* Example usage to set ERC-1967 implementation slot:
* \`\`\`solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
Expand All @@ -27,6 +28,8 @@ pragma solidity ^0.8.20;
* }
* }
* \`\`\`
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
`;

Expand Down
20 changes: 10 additions & 10 deletions test/helpers/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ const ImplementationLabel = 'eip1967.proxy.implementation';
const AdminLabel = 'eip1967.proxy.admin';
const BeaconLabel = 'eip1967.proxy.beacon';

const erc1967slot = label => ethers.toBeHex(ethers.toBigInt(ethers.id(label)) - 1n);
const erc7201slot = label => ethers.toBeHex(ethers.toBigInt(ethers.keccak256(erc1967slot(label))) & ~0xffn);
const erc1967Slot = label => ethers.toBeHex(ethers.toBigInt(ethers.id(label)) - 1n);
const erc7201Slot = label => ethers.toBeHex(ethers.toBigInt(ethers.keccak256(erc1967Slot(label))) & ~0xffn);
const erc7201format = contractName => `openzeppelin.storage.${contractName}`;

const getSlot = (address, slot) =>
ethers.provider.getStorage(address, ethers.isBytesLike(slot) ? slot : erc1967slot(slot));
ethers.provider.getStorage(address, ethers.isBytesLike(slot) ? slot : erc1967Slot(slot));

const setSlot = (address, slot, value) =>
Promise.all([
ethers.isAddressable(address) ? address.getAddress() : Promise.resolve(address),
ethers.isAddressable(value) ? value.getAddress() : Promise.resolve(value),
]).then(([address, value]) => setStorageAt(address, ethers.isBytesLike(slot) ? slot : erc1967slot(slot), value));
]).then(([address, value]) => setStorageAt(address, ethers.isBytesLike(slot) ? slot : erc1967Slot(slot), value));

const getAddressInSlot = (address, slot) =>
getSlot(address, slot).then(slotValue => ethers.AbiCoder.defaultAbiCoder().decode(['address'], slotValue)[0]);
Expand All @@ -25,7 +25,7 @@ const upgradeableSlot = (contractName, offset) => {
try {
// Try to get the artifact paths, will throw if it doesn't exist
artifacts._getArtifactPathSync(`${contractName}Upgradeable`);
return offset + ethers.toBigInt(erc7201slot(erc7201format(contractName)));
return offset + ethers.toBigInt(erc7201Slot(erc7201format(contractName)));
} catch (_) {
return offset;
}
Expand All @@ -35,11 +35,11 @@ module.exports = {
ImplementationLabel,
AdminLabel,
BeaconLabel,
ImplementationSlot: erc1967slot(ImplementationLabel),
AdminSlot: erc1967slot(AdminLabel),
BeaconSlot: erc1967slot(BeaconLabel),
erc1967slot,
erc7201slot,
ImplementationSlot: erc1967Slot(ImplementationLabel),
AdminSlot: erc1967Slot(AdminLabel),
BeaconSlot: erc1967Slot(BeaconLabel),
erc1967Slot,
erc7201Slot,
erc7201format,
setSlot,
getSlot,
Expand Down
6 changes: 3 additions & 3 deletions test/utils/SlotDerivation.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { erc1967slot, erc7201slot } = require('../helpers/storage');
const { erc1967Slot, erc7201Slot } = require('../helpers/storage');
const { generators } = require('../helpers/random');

async function fixture() {
Expand All @@ -19,11 +19,11 @@ describe('SlotDerivation', function () {
const namespace = 'example.main';

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

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

Expand Down