Skip to content

Commit

Permalink
Allow Initializable versions greater than 256 (#4460)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco <fg@frang.io>
  • Loading branch information
ernestognw and frangio committed Aug 7, 2023
1 parent 54a235f commit 70578bb
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-pumpkins-exercise.md
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': major
---

`Initializable`: Use the namespaced storage pattern to avoid putting critical variables in slot 0. Allow reinitializer versions greater than 256.
8 changes: 4 additions & 4 deletions contracts/mocks/InitializableMock.sol
Expand Up @@ -79,23 +79,23 @@ contract ChildConstructorInitializableMock is ConstructorInitializableMock {
contract ReinitializerMock is Initializable {
uint256 public counter;

function getInitializedVersion() public view returns (uint8) {
function getInitializedVersion() public view returns (uint64) {
return _getInitializedVersion();
}

function initialize() public initializer {
doStuff();
}

function reinitialize(uint8 i) public reinitializer(i) {
function reinitialize(uint64 i) public reinitializer(i) {
doStuff();
}

function nestedReinitialize(uint8 i, uint8 j) public reinitializer(i) {
function nestedReinitialize(uint64 i, uint64 j) public reinitializer(i) {
reinitialize(j);
}

function chainReinitialize(uint8 i, uint8 j) public {
function chainReinitialize(uint64 i, uint64 j) public {
reinitialize(i);
reinitialize(j);
}
Expand Down
82 changes: 57 additions & 25 deletions contracts/proxy/utils/Initializable.sol
Expand Up @@ -55,14 +55,27 @@ pragma solidity ^0.8.20;
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
uint8 private _initialized;
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}

/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1))
bytes32 private constant _INITIALIZABLE_STORAGE =
0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0e;

/**
* @dev The contract is already initialized.
Expand All @@ -77,7 +90,7 @@ abstract contract Initializable {
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
event Initialized(uint64 version);

/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
Expand All @@ -89,17 +102,20 @@ abstract contract Initializable {
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
if (!(isTopLevelCall && _initialized < 1) && !(address(this).code.length == 0 && _initialized == 1)) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();

bool isTopLevelCall = !$._initializing;
if (!(isTopLevelCall && $._initialized < 1) && !(address(this).code.length == 0 && $._initialized == 1)) {
revert AlreadyInitialized();
}
_initialized = 1;
$._initialized = 1;
if (isTopLevelCall) {
_initializing = true;
$._initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
$._initializing = false;
emit Initialized(1);
}
}
Expand All @@ -122,14 +138,17 @@ abstract contract Initializable {
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
if (_initializing || _initialized >= version) {
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();

if ($._initializing || $._initialized >= version) {
revert AlreadyInitialized();
}
_initialized = version;
_initializing = true;
$._initialized = version;
$._initializing = true;
_;
_initializing = false;
$._initializing = false;
emit Initialized(version);
}

Expand All @@ -146,7 +165,7 @@ abstract contract Initializable {
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_initializing) {
if (!_isInitializing()) {
revert NotInitializing();
}
}
Expand All @@ -160,26 +179,39 @@ abstract contract Initializable {
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
if (_initializing) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();

if ($._initializing) {
revert AlreadyInitialized();
}
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}

/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}

/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
return _getInitializableStorage()._initializing;
}

/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := _INITIALIZABLE_STORAGE
}
}
}
7 changes: 7 additions & 0 deletions test/helpers/constants.js
@@ -0,0 +1,7 @@
const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString();
const MAX_UINT64 = web3.utils.toBN(1).shln(64).subn(1).toString();

module.exports = {
MAX_UINT48,
MAX_UINT64,
};
3 changes: 1 addition & 2 deletions test/metatx/ERC2771Context.test.js
@@ -1,6 +1,7 @@
const ethSigUtil = require('eth-sig-util');
const Wallet = require('ethereumjs-wallet').default;
const { getDomain, domainType } = require('../helpers/eip712');
const { MAX_UINT48 } = require('../helpers/constants');

const { expectEvent } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
Expand All @@ -14,8 +15,6 @@ const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior');
contract('ERC2771Context', function (accounts) {
const [, trustedForwarder] = accounts;

const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString();

beforeEach(async function () {
this.forwarder = await ERC2771Forwarder.new('ERC2771Forwarder');
this.recipient = await ERC2771ContextMock.new(this.forwarder.address);
Expand Down
3 changes: 2 additions & 1 deletion test/proxy/utils/Initializable.test.js
@@ -1,6 +1,7 @@
const { expectEvent } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const { expectRevertCustomError } = require('../../helpers/customError');
const { MAX_UINT64 } = require('../../helpers/constants');

const InitializableMock = artifacts.require('InitializableMock');
const ConstructorInitializableMock = artifacts.require('ConstructorInitializableMock');
Expand Down Expand Up @@ -213,7 +214,7 @@ contract('Initializable', function () {
it('old and new patterns in good sequence', async function () {
const ok = await DisableOk.new();
await expectEvent.inConstruction(ok, 'Initialized', { version: '1' });
await expectEvent.inConstruction(ok, 'Initialized', { version: '255' });
await expectEvent.inConstruction(ok, 'Initialized', { version: MAX_UINT64 });
});
});
});

0 comments on commit 70578bb

Please sign in to comment.