Skip to content

Commit ff85c7b

Browse files
authored
Make ERC1967Upgrades a library instead of an abstract contract (#4325)
1 parent 05ef692 commit ff85c7b

File tree

11 files changed

+89
-61
lines changed

11 files changed

+89
-61
lines changed

.changeset/grumpy-worms-tease.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': major
3+
---
4+
5+
`ERC1967Utils`: Refactor the `ERC1967Upgrade` abstract contract as a library.

contracts/mocks/proxy/UUPSLegacy.sol

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import "./UUPSUpgradeableMock.sol";
77
// This contract implements the pre-4.5 UUPS upgrade function with a rollback test.
88
// It's used to test that newer UUPS contracts are considered valid upgrades by older UUPS contracts.
99
contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock {
10-
// Inlined from ERC1967Upgrade
10+
// Inlined from ERC1967Utils
1111
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
1212

13-
// ERC1967Upgrade._setImplementation is private so we reproduce it here.
13+
// ERC1967Utils._setImplementation is private so we reproduce it here.
1414
// An extra underscore prevents a name clash error.
1515
function __setImplementation(address newImplementation) private {
1616
require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract");
17-
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
17+
StorageSlot.getAddressSlot(ERC1967Utils.IMPLEMENTATION_SLOT).value = newImplementation;
1818
}
1919

2020
function _upgradeToAndCallSecureLegacyV1(address newImplementation, bytes memory data, bool forceCall) internal {
21-
address oldImplementation = _getImplementation();
21+
address oldImplementation = ERC1967Utils.getImplementation();
2222

2323
// Initial upgrade and setup call
2424
__setImplementation(newImplementation);
@@ -34,9 +34,12 @@ contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock {
3434
Address.functionDelegateCall(newImplementation, abi.encodeCall(this.upgradeTo, (oldImplementation)));
3535
rollbackTesting.value = false;
3636
// Check rollback was effective
37-
require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
37+
require(
38+
oldImplementation == ERC1967Utils.getImplementation(),
39+
"ERC1967Utils: upgrade breaks further upgrades"
40+
);
3841
// Finally reset to the new implementation and log the upgrade
39-
_upgradeTo(newImplementation);
42+
ERC1967Utils.upgradeTo(newImplementation);
4043
}
4144
}
4245

contracts/mocks/proxy/UUPSUpgradeableMock.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ contract UUPSUpgradeableMock is NonUpgradeableMock, UUPSUpgradeable {
2323

2424
contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock {
2525
function upgradeTo(address newImplementation) public override {
26-
_upgradeToAndCall(newImplementation, bytes(""), false);
26+
ERC1967Utils.upgradeToAndCall(newImplementation, bytes(""), false);
2727
}
2828

2929
function upgradeToAndCall(address newImplementation, bytes memory data) public payable override {
30-
_upgradeToAndCall(newImplementation, data, false);
30+
ERC1967Utils.upgradeToAndCall(newImplementation, data, false);
3131
}
3232
}

contracts/proxy/ERC1967/ERC1967Proxy.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@
44
pragma solidity ^0.8.19;
55

66
import "../Proxy.sol";
7-
import "./ERC1967Upgrade.sol";
7+
import "./ERC1967Utils.sol";
88

99
/**
1010
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
1111
* implementation address that can be changed. This address is stored in storage in the location specified by
1212
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
1313
* implementation behind the proxy.
1414
*/
15-
contract ERC1967Proxy is Proxy, ERC1967Upgrade {
15+
contract ERC1967Proxy is Proxy {
1616
/**
1717
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
1818
*
1919
* If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
2020
* function call, and allows initializing the storage of the proxy like a Solidity constructor.
2121
*/
2222
constructor(address _logic, bytes memory _data) payable {
23-
_upgradeToAndCall(_logic, _data, false);
23+
ERC1967Utils.upgradeToAndCall(_logic, _data, false);
2424
}
2525

2626
/**
@@ -31,6 +31,6 @@ contract ERC1967Proxy is Proxy, ERC1967Upgrade {
3131
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
3232
*/
3333
function _implementation() internal view virtual override returns (address impl) {
34-
return _getImplementation();
34+
return ERC1967Utils.getImplementation();
3535
}
3636
}

contracts/proxy/ERC1967/ERC1967Upgrade.sol renamed to contracts/proxy/ERC1967/ERC1967Utils.sol

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: MIT
2-
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
2+
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Utils.sol)
33

4-
pragma solidity ^0.8.19;
4+
pragma solidity ^0.8.20;
55

66
import "../beacon/IBeacon.sol";
77
import "../../interfaces/IERC1967.sol";
@@ -15,7 +15,24 @@ import "../../utils/StorageSlot.sol";
1515
*
1616
* _Available since v4.1._
1717
*/
18-
abstract contract ERC1967Upgrade is IERC1967 {
18+
library ERC1967Utils {
19+
// We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
20+
// This will be fixed in Solidity 0.8.21. At that point we should remove these events.
21+
/**
22+
* @dev Emitted when the implementation is upgraded.
23+
*/
24+
event Upgraded(address indexed implementation);
25+
26+
/**
27+
* @dev Emitted when the admin account has changed.
28+
*/
29+
event AdminChanged(address previousAdmin, address newAdmin);
30+
31+
/**
32+
* @dev Emitted when the beacon is changed.
33+
*/
34+
event BeaconUpgraded(address indexed beacon);
35+
1936
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
2037
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
2138

@@ -24,7 +41,8 @@ abstract contract ERC1967Upgrade is IERC1967 {
2441
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
2542
* validated in the constructor.
2643
*/
27-
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
44+
// solhint-disable-next-line private-vars-leading-underscore
45+
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
2846

2947
/**
3048
* @dev The `implementation` of the proxy is invalid.
@@ -49,8 +67,8 @@ abstract contract ERC1967Upgrade is IERC1967 {
4967
/**
5068
* @dev Returns the current implementation address.
5169
*/
52-
function _getImplementation() internal view returns (address) {
53-
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
70+
function getImplementation() internal view returns (address) {
71+
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
5472
}
5573

5674
/**
@@ -60,26 +78,26 @@ abstract contract ERC1967Upgrade is IERC1967 {
6078
if (newImplementation.code.length == 0) {
6179
revert ERC1967InvalidImplementation(newImplementation);
6280
}
63-
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
81+
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
6482
}
6583

6684
/**
6785
* @dev Perform implementation upgrade
6886
*
69-
* Emits an {Upgraded} event.
87+
* Emits an {IERC1967-Upgraded} event.
7088
*/
71-
function _upgradeTo(address newImplementation) internal {
89+
function upgradeTo(address newImplementation) internal {
7290
_setImplementation(newImplementation);
7391
emit Upgraded(newImplementation);
7492
}
7593

7694
/**
7795
* @dev Perform implementation upgrade with additional setup call.
7896
*
79-
* Emits an {Upgraded} event.
97+
* Emits an {IERC1967-Upgraded} event.
8098
*/
81-
function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
82-
_upgradeTo(newImplementation);
99+
function upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
100+
upgradeTo(newImplementation);
83101
if (data.length > 0 || forceCall) {
84102
Address.functionDelegateCall(newImplementation, data);
85103
}
@@ -88,24 +106,24 @@ abstract contract ERC1967Upgrade is IERC1967 {
88106
/**
89107
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
90108
*
91-
* Emits an {Upgraded} event.
109+
* Emits an {IERC1967-Upgraded} event.
92110
*/
93-
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
111+
function upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
94112
// Upgrades from old implementations will perform a rollback test. This test requires the new
95113
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
96114
// this special case will break upgrade paths from old UUPS implementation to new ones.
97115
if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
98116
_setImplementation(newImplementation);
99117
} else {
100118
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
101-
if (slot != _IMPLEMENTATION_SLOT) {
119+
if (slot != IMPLEMENTATION_SLOT) {
102120
revert ERC1967UnsupportedProxiableUUID(slot);
103121
}
104122
} catch {
105123
// The implementation is not UUPS
106124
revert ERC1967InvalidImplementation(newImplementation);
107125
}
108-
_upgradeToAndCall(newImplementation, data, forceCall);
126+
upgradeToAndCall(newImplementation, data, forceCall);
109127
}
110128
}
111129

@@ -114,7 +132,8 @@ abstract contract ERC1967Upgrade is IERC1967 {
114132
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
115133
* validated in the constructor.
116134
*/
117-
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
135+
// solhint-disable-next-line private-vars-leading-underscore
136+
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
118137

119138
/**
120139
* @dev Returns the current admin.
@@ -123,8 +142,8 @@ abstract contract ERC1967Upgrade is IERC1967 {
123142
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
124143
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
125144
*/
126-
function _getAdmin() internal view returns (address) {
127-
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
145+
function getAdmin() internal view returns (address) {
146+
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
128147
}
129148

130149
/**
@@ -134,30 +153,31 @@ abstract contract ERC1967Upgrade is IERC1967 {
134153
if (newAdmin == address(0)) {
135154
revert ERC1967InvalidAdmin(address(0));
136155
}
137-
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
156+
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
138157
}
139158

140159
/**
141160
* @dev Changes the admin of the proxy.
142161
*
143-
* Emits an {AdminChanged} event.
162+
* Emits an {IERC1967-AdminChanged} event.
144163
*/
145-
function _changeAdmin(address newAdmin) internal {
146-
emit AdminChanged(_getAdmin(), newAdmin);
164+
function changeAdmin(address newAdmin) internal {
165+
emit AdminChanged(getAdmin(), newAdmin);
147166
_setAdmin(newAdmin);
148167
}
149168

150169
/**
151170
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
152-
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
171+
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) and is validated in the constructor.
153172
*/
154-
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
173+
// solhint-disable-next-line private-vars-leading-underscore
174+
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
155175

156176
/**
157177
* @dev Returns the current beacon.
158178
*/
159-
function _getBeacon() internal view returns (address) {
160-
return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
179+
function getBeacon() internal view returns (address) {
180+
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
161181
}
162182

163183
/**
@@ -173,16 +193,16 @@ abstract contract ERC1967Upgrade is IERC1967 {
173193
revert ERC1967InvalidImplementation(beaconImplementation);
174194
}
175195

176-
StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
196+
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
177197
}
178198

179199
/**
180200
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
181201
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
182202
*
183-
* Emits a {BeaconUpgraded} event.
203+
* Emits an {IERC1967-BeaconUpgraded} event.
184204
*/
185-
function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
205+
function upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
186206
_setBeacon(newBeacon);
187207
emit BeaconUpgraded(newBeacon);
188208
if (data.length > 0 || forceCall) {

contracts/proxy/README.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Most of the proxies below are built on an abstract base contract.
1111
1212
In order to avoid clashes with the storage variables of the implementation contract behind a proxy, we use https://eips.ethereum.org/EIPS/eip-1967[EIP1967] storage slots.
1313

14-
- {ERC1967Upgrade}: Internal functions to get and set the storage slots defined in EIP1967.
14+
- {ERC1967Utils}: Internal functions to get and set the storage slots defined in EIP1967.
1515
- {ERC1967Proxy}: A proxy using EIP1967 storage slots. Not upgradeable by default.
1616
1717
There are two alternative ways to add upgradeability to an ERC1967 proxy. Their differences are explained below in <<transparent-vs-uups>>.
@@ -60,7 +60,7 @@ The current implementation of this security mechanism uses https://eips.ethereum
6060

6161
{{ERC1967Proxy}}
6262

63-
{{ERC1967Upgrade}}
63+
{{ERC1967Utils}}
6464

6565
== Transparent Proxy
6666

contracts/proxy/beacon/BeaconProxy.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pragma solidity ^0.8.19;
55

66
import "./IBeacon.sol";
77
import "../Proxy.sol";
8-
import "../ERC1967/ERC1967Upgrade.sol";
8+
import "../ERC1967/ERC1967Utils.sol";
99

1010
/**
1111
* @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
@@ -15,7 +15,7 @@ import "../ERC1967/ERC1967Upgrade.sol";
1515
*
1616
* _Available since v3.4._
1717
*/
18-
contract BeaconProxy is Proxy, ERC1967Upgrade {
18+
contract BeaconProxy is Proxy {
1919
/**
2020
* @dev Initializes the proxy with `beacon`.
2121
*
@@ -28,13 +28,13 @@ contract BeaconProxy is Proxy, ERC1967Upgrade {
2828
* - `beacon` must be a contract with the interface {IBeacon}.
2929
*/
3030
constructor(address beacon, bytes memory data) payable {
31-
_upgradeBeaconToAndCall(beacon, data, false);
31+
ERC1967Utils.upgradeBeaconToAndCall(beacon, data, false);
3232
}
3333

3434
/**
3535
* @dev Returns the current implementation address of the associated beacon.
3636
*/
3737
function _implementation() internal view virtual override returns (address) {
38-
return IBeacon(_getBeacon()).implementation();
38+
return IBeacon(ERC1967Utils.getBeacon()).implementation();
3939
}
4040
}

contracts/proxy/transparent/TransparentUpgradeableProxy.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ contract TransparentUpgradeableProxy is ERC1967Proxy {
6767
* optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
6868
*/
6969
constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
70-
_changeAdmin(admin_);
70+
ERC1967Utils.changeAdmin(admin_);
7171
}
7272

7373
/**
7474
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior
7575
*/
7676
function _fallback() internal virtual override {
77-
if (msg.sender == _getAdmin()) {
77+
if (msg.sender == ERC1967Utils.getAdmin()) {
7878
bytes memory ret;
7979
bytes4 selector = msg.sig;
8080
if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) {
@@ -103,7 +103,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy {
103103
_requireZeroValue();
104104

105105
address newAdmin = abi.decode(msg.data[4:], (address));
106-
_changeAdmin(newAdmin);
106+
ERC1967Utils.changeAdmin(newAdmin);
107107

108108
return "";
109109
}
@@ -115,7 +115,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy {
115115
_requireZeroValue();
116116

117117
address newImplementation = abi.decode(msg.data[4:], (address));
118-
_upgradeToAndCall(newImplementation, bytes(""), false);
118+
ERC1967Utils.upgradeToAndCall(newImplementation, bytes(""), false);
119119

120120
return "";
121121
}
@@ -127,7 +127,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy {
127127
*/
128128
function _dispatchUpgradeToAndCall() private returns (bytes memory) {
129129
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
130-
_upgradeToAndCall(newImplementation, data, true);
130+
ERC1967Utils.upgradeToAndCall(newImplementation, data, true);
131131

132132
return "";
133133
}

0 commit comments

Comments
 (0)