Skip to content

Commit e5fc1d7

Browse files
zmalatraxjaybuidl
authored andcommitted
feat(proxy): add upgradability contracts
1 parent be13916 commit e5fc1d7

File tree

3 files changed

+478
-0
lines changed

3 files changed

+478
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol>
3+
4+
pragma solidity 0.8.18;
5+
6+
/**
7+
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
8+
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
9+
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
10+
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
11+
*
12+
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
13+
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
14+
* case an upgrade adds a module that needs to be initialized.
15+
*
16+
* For example:
17+
*
18+
* ```solidity
19+
* contract MyToken is ERC20Upgradeable {
20+
* function initialize() initializer public {
21+
* __ERC20_init("MyToken", "MTK");
22+
* }
23+
* }
24+
*
25+
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
26+
* function initializeV2() reinitializer(2) public {
27+
* __ERC20Permit_init("MyToken");
28+
* }
29+
* }
30+
* ```
31+
*
32+
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
33+
* possible by providing the encoded function call as the `_data` argument to the proxy constructor
34+
*
35+
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
36+
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
37+
*
38+
* [CAUTION]
39+
* ====
40+
* Avoid leaving a contract uninitialized.
41+
*
42+
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
43+
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
44+
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
45+
*
46+
*/
47+
abstract contract Initializable {
48+
/**
49+
* @dev Storage of the initializable contract.
50+
*
51+
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
52+
* when using with upgradeable contracts.
53+
*
54+
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
55+
*/
56+
struct InitializableStorage {
57+
/**
58+
* @dev Indicates that the contract has been initialized.
59+
*/
60+
uint64 _initialized;
61+
/**
62+
* @dev Indicates that the contract is in the process of being initialized.
63+
*/
64+
bool _initializing;
65+
}
66+
67+
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1))
68+
bytes32 private constant _INITIALIZABLE_STORAGE =
69+
0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0e;
70+
71+
/**
72+
* @dev The contract is already initialized.
73+
*/
74+
error AlreadyInitialized();
75+
76+
/**
77+
* @dev The contract is not initializing.
78+
*/
79+
error NotInitializing();
80+
81+
/**
82+
* @dev Triggered when the contract has been initialized or reinitialized.
83+
*/
84+
event Initialized(uint64 version);
85+
86+
/**
87+
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
88+
* `onlyInitializing` functions can be used to initialize parent contracts.
89+
*
90+
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
91+
* constructor.
92+
*
93+
* Emits an {Initialized} event.
94+
*/
95+
modifier initializer() {
96+
// solhint-disable-next-line var-name-mixedcase
97+
InitializableStorage storage $ = _getInitializableStorage();
98+
99+
bool isTopLevelCall = !$._initializing;
100+
uint64 initialized = $._initialized;
101+
if (!(isTopLevelCall && initialized < 1) && !(address(this).code.length == 0 && initialized == 1)) {
102+
revert AlreadyInitialized();
103+
}
104+
$._initialized = 1;
105+
if (isTopLevelCall) {
106+
$._initializing = true;
107+
}
108+
_;
109+
if (isTopLevelCall) {
110+
$._initializing = false;
111+
emit Initialized(1);
112+
}
113+
}
114+
115+
/**
116+
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
117+
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
118+
* used to initialize parent contracts.
119+
*
120+
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
121+
* are added through upgrades and that require initialization.
122+
*
123+
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
124+
* cannot be nested. If one is invoked in the context of another, execution will revert.
125+
*
126+
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
127+
* a contract, executing them in the right order is up to the developer or operator.
128+
*
129+
* WARNING: setting the version to 255 will prevent any future reinitialization.
130+
*
131+
* Emits an {Initialized} event.
132+
*/
133+
modifier reinitializer(uint64 version) {
134+
// solhint-disable-next-line var-name-mixedcase
135+
InitializableStorage storage $ = _getInitializableStorage();
136+
137+
if ($._initializing || $._initialized >= version) {
138+
revert AlreadyInitialized();
139+
}
140+
$._initialized = version;
141+
$._initializing = true;
142+
_;
143+
$._initializing = false;
144+
emit Initialized(version);
145+
}
146+
147+
/**
148+
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
149+
* {initializer} and {reinitializer} modifiers, directly or indirectly.
150+
*/
151+
modifier onlyInitializing() {
152+
_checkInitializing();
153+
_;
154+
}
155+
156+
/**
157+
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
158+
*/
159+
function _checkInitializing() internal view virtual {
160+
if (!_isInitializing()) {
161+
revert NotInitializing();
162+
}
163+
}
164+
165+
/**
166+
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
167+
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
168+
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
169+
* through proxies.
170+
*
171+
* Emits an {Initialized} event the first time it is successfully executed.
172+
*/
173+
function _disableInitializers() internal virtual {
174+
// solhint-disable-next-line var-name-mixedcase
175+
InitializableStorage storage $ = _getInitializableStorage();
176+
177+
if ($._initializing) {
178+
revert AlreadyInitialized();
179+
}
180+
if ($._initialized != type(uint64).max) {
181+
$._initialized = type(uint64).max;
182+
emit Initialized(type(uint64).max);
183+
}
184+
}
185+
186+
/**
187+
* @dev Returns the highest version that has been initialized. See {reinitializer}.
188+
*/
189+
function _getInitializedVersion() internal view returns (uint64) {
190+
return _getInitializableStorage()._initialized;
191+
}
192+
193+
/**
194+
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
195+
*/
196+
function _isInitializing() internal view returns (bool) {
197+
return _getInitializableStorage()._initializing;
198+
}
199+
200+
/**
201+
* @dev Returns a pointer to the storage namespace.
202+
*/
203+
// solhint-disable-next-line var-name-mixedcase
204+
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
205+
assembly {
206+
$.slot := _INITIALIZABLE_STORAGE
207+
}
208+
}
209+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//SPDX-License-Identifier: MIT
2+
// Adapted from <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol>
3+
4+
/**
5+
* @authors: [@malatrax]
6+
* @reviewers: []
7+
* @auditors: []
8+
* @bounties: []
9+
* @deployments: []
10+
*/
11+
pragma solidity 0.8.18;
12+
13+
/**
14+
* @title UUPS Proxiable
15+
* @author Simon Malatrait <simon.malatrait@grenoble-inp.org>
16+
* @dev This contract implements an upgradeability mechanism designed for UUPS proxies.
17+
* The functions included here can perform an upgrade of an UUPS Proxy, when this contract is set as the implementation behind such a proxy.
18+
*
19+
* IMPORTANT: A UUPS proxy requires its upgradeability functions to be in the implementation as opposed to the transparent proxy.
20+
* This means that if the proxy is upgraded to an implementation that does not support this interface, it will no longer be upgradeable.
21+
*
22+
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
23+
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
24+
* `UUPSProxiable` with a custom implementation of upgrades.
25+
*
26+
* The `_authorizeUpgrade` function must be overridden to include access restriction to the upgrade mechanism.
27+
*/
28+
abstract contract UUPSProxiable {
29+
// ************************************* //
30+
// * Event * //
31+
// ************************************* //
32+
33+
/**
34+
* Emitted when the `implementation` has been successfully upgraded.
35+
* @param newImplementation Address of the new implementation the proxy is now forwarding calls to.
36+
*/
37+
event Upgraded(address indexed newImplementation);
38+
39+
// ************************************* //
40+
// * Error * //
41+
// ************************************* //
42+
43+
/**
44+
* @dev The call is from an unauthorized context.
45+
*/
46+
error UUPSUnauthorizedCallContext();
47+
48+
/**
49+
* @dev The storage `slot` is unsupported as a UUID.
50+
*/
51+
error UUPSUnsupportedProxiableUUID(bytes32 slot);
52+
53+
/// The `implementation` is not UUPS-compliant
54+
error InvalidImplementation(address implementation);
55+
56+
/// Failed Delegated call
57+
error FailedDelegateCall();
58+
59+
// ************************************* //
60+
// * Storage * //
61+
// ************************************* //
62+
63+
/**
64+
* @dev Storage slot with the address of the current implementation.
65+
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
66+
* validated in the constructor.
67+
* NOTE: bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
68+
*/
69+
bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
70+
71+
/**
72+
* @dev Storage variable of the proxiable contract address.
73+
* It is used to check whether or not the current call is from the proxy.
74+
*/
75+
address private immutable __self = address(this);
76+
77+
// ************************************* //
78+
// * Governance * //
79+
// ************************************* //
80+
81+
/**
82+
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract.
83+
* @dev Called by {upgradeToAndCall}.
84+
*/
85+
function _authorizeUpgrade(address newImplementation) internal virtual;
86+
87+
// ************************************* //
88+
// * State Modifiers * //
89+
// ************************************* //
90+
91+
/**
92+
* @dev Upgrade mechanism including access control and UUPS-compliance.
93+
* @param newImplementation Address of the new implementation contract.
94+
* @param data Data used in a delegate call to `newImplementation` if non-empty. This will typically be an encoded
95+
* function call, and allows initializing the storage of the proxy like a Solidity constructor.
96+
*
97+
* @dev Reverts if the execution is not performed via delegatecall or the execution
98+
* context is not of a proxy with an ERC1967-compliant implementation pointing to self.
99+
*/
100+
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual {
101+
_authorizeUpgrade(newImplementation);
102+
103+
/* Check that the execution is being performed through a delegatecall call and that the execution context is
104+
a proxy contract with an implementation (as defined in ERC1967) pointing to self. */
105+
if (address(this) == __self || _getImplementation() != __self) {
106+
revert UUPSUnauthorizedCallContext();
107+
}
108+
109+
try UUPSProxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
110+
if (slot != IMPLEMENTATION_SLOT) {
111+
revert UUPSUnsupportedProxiableUUID(slot);
112+
}
113+
// Store the new implementation address to the implementation storage slot.
114+
assembly {
115+
sstore(IMPLEMENTATION_SLOT, newImplementation)
116+
}
117+
emit Upgraded(newImplementation);
118+
119+
if (data.length != 0) {
120+
// The return data is not checked (checking, in case of success, that the newImplementation code is non-empty if the return data is empty) because the authorized callee is trusted.
121+
(bool success, ) = newImplementation.delegatecall(data);
122+
if (!success) {
123+
revert FailedDelegateCall();
124+
}
125+
}
126+
} catch {
127+
revert InvalidImplementation(newImplementation);
128+
}
129+
}
130+
131+
// ************************************* //
132+
// * Public Views * //
133+
// ************************************* //
134+
135+
/**
136+
* @dev Implementation of the ERC1822 `proxiableUUID` function. This returns the storage slot used by the
137+
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
138+
*
139+
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
140+
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
141+
* function revert if invoked through a proxy. This is guaranteed by the if statement.
142+
*/
143+
function proxiableUUID() external view virtual returns (bytes32) {
144+
if (address(this) != __self) {
145+
// Must not be called through delegatecall
146+
revert UUPSUnauthorizedCallContext();
147+
}
148+
return IMPLEMENTATION_SLOT;
149+
}
150+
151+
// ************************************* //
152+
// * Internal Views * //
153+
// ************************************* //
154+
155+
function _getImplementation() internal view returns (address implementation) {
156+
assembly {
157+
implementation := sload(IMPLEMENTATION_SLOT)
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)