Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow the re-initialization of contracts #3232

Merged
merged 20 commits into from Mar 22, 2022
Merged
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 62 additions & 15 deletions contracts/proxy/utils/Initializable.sol
Expand Up @@ -11,6 +11,26 @@ import "../../utils/Address.sol";
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
Expand All @@ -22,21 +42,24 @@ import "../../utils/Address.sol";
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
* initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_preventInitialize} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() initializer {}
* constructor() {
* _preventInitialize();
* }
* ```
* ====
*/
abstract contract Initializable {
frangio marked this conversation as resolved.
Show resolved Hide resolved
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
bool private _initialized;
uint8 private _initialized;

/**
* @dev Indicates that the contract is in the process of being initialized.
Expand All @@ -47,19 +70,21 @@ abstract contract Initializable {
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
// contract may have been reentered.
require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");

bool isTopLevelCall = !_initializing;
bool isTopLevelCall = _setInitializedVersion(1);
Amxx marked this conversation as resolved.
Show resolved Hide resolved
_;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
_initializing = false;
}
}

/**
* @dev Modifier to allow the execution of `onlyInitializing`-protected methods, after the original initialization
* step, through "reinitializer". These "reinitializer" are essential to configure modules that are added through
* upgrades and that require an initialization step.
*/
modifier reinitializer(uint8 version) {
bool isTopLevelCall = _setInitializedVersion(version);
_;

if (isTopLevelCall) {
_initializing = false;
}
Expand All @@ -74,7 +99,29 @@ abstract contract Initializable {
_;
}

function _isConstructor() private view returns (bool) {
return !Address.isContract(address(this));
/**
* @dev Locks the contract, preventing any futur re-initialization. This cannot be part of an initializer call.
* Calling this in the constructor of an contract will prevent any direct initialization & re-initialization.
* It is recommended to use that to lock "implementation" contract that are designed to be called through proxies.
*/
function _preventInitialize() internal virtual {
if (_setInitializedVersion(type(uint8).max)) {
_initializing = false;
}
Amxx marked this conversation as resolved.
Show resolved Hide resolved
}

function _setInitializedVersion(uint8 version) private returns (bool) {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, and for the lowest level
// of initializers, because in other contexts the contract may have been reentered.
if (_initializing) {
require(version == 1 && !Address.isContract(address(this)), "Initializable: contract is already initialized");
Amxx marked this conversation as resolved.
Show resolved Hide resolved
return false;
} else {
require(_initialized < version, "Initializable: contract is already initialized");
_initializing = true;
_initialized = version;
return true;
}
}
}