-
Notifications
You must be signed in to change notification settings - Fork 11.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement UUPS proxy (ERC1822) (#2542)
Co-authored-by: Francisco Giordano <[email protected]>
- Loading branch information
Showing
16 changed files
with
595 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../utils/StorageSlot.sol"; | ||
|
||
contract StorageSlotMock { | ||
using StorageSlot for bytes32; | ||
function setBoolean(bytes32 slot, bool value) public { slot.getBooleanSlot().value = value; } | ||
function setAddress(bytes32 slot, address value) public { slot.getAddressSlot().value = value; } | ||
function setBytes32(bytes32 slot, bytes32 value) public { slot.getBytes32Slot().value = value; } | ||
function setUint256(bytes32 slot, uint256 value) public { slot.getUint256Slot().value = value; } | ||
function getBoolean(bytes32 slot) public view returns (bool) { return slot.getBooleanSlot().value; } | ||
function getAddress(bytes32 slot) public view returns (address) { return slot.getAddressSlot().value; } | ||
function getBytes32(bytes32 slot) public view returns (bytes32) { return slot.getBytes32Slot().value; } | ||
function getUint256(bytes32 slot) public view returns (uint256) { return slot.getUint256Slot().value; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../CountersImpl.sol"; | ||
import "../../proxy/UUPS/UUPSUpgradeable.sol"; | ||
|
||
contract UUPSUpgradeableMock is CountersImpl, UUPSUpgradeable { | ||
// Not having any checks in this function is dangerous! Do not do this outside tests! | ||
function _authorizeUpgrade(address) internal virtual override {} | ||
} | ||
|
||
contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock { | ||
function upgradeTo(address newImplementation) external virtual override { | ||
ERC1967Upgrade._upgradeToAndCall(newImplementation, bytes(""), false); | ||
} | ||
|
||
function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual override { | ||
ERC1967Upgrade._upgradeToAndCall(newImplementation, data, false); | ||
} | ||
} | ||
|
||
contract UUPSUpgradeableBrokenMock is UUPSUpgradeableMock { | ||
function upgradeTo(address) external virtual override { | ||
// pass | ||
} | ||
|
||
function upgradeToAndCall(address, bytes memory) external payable virtual override { | ||
// pass | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../beacon/IBeacon.sol"; | ||
import "../../utils/Address.sol"; | ||
import "../../utils/StorageSlot.sol"; | ||
|
||
/** | ||
* @dev This abstract contract provides setters and getters for the different | ||
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] storage slots. | ||
*/ | ||
abstract contract ERC1967Storage { | ||
/** | ||
* @dev Storage slot with the address of the current implementation. | ||
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is | ||
* validated in the constructor. | ||
*/ | ||
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; | ||
|
||
/** | ||
* @dev Returns the current implementation address. | ||
*/ | ||
function _getImplementation() internal view returns (address) { | ||
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; | ||
} | ||
|
||
/** | ||
* @dev Stores a new address in the EIP1967 implementation slot. | ||
*/ | ||
function _setImplementation(address newImplementation) internal { | ||
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); | ||
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; | ||
} | ||
|
||
/** | ||
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. | ||
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. | ||
*/ | ||
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; | ||
|
||
/** | ||
* @dev Returns the current beacon. | ||
*/ | ||
function _getBeacon() internal view returns (address) { | ||
return StorageSlot.getAddressSlot(_BEACON_SLOT).value; | ||
} | ||
|
||
/** | ||
* @dev Stores a new beacon in the EIP1967 beacon slot. | ||
*/ | ||
function _setBeacon(address newBeacon) internal { | ||
require( | ||
Address.isContract(newBeacon), | ||
"ERC1967: new beacon is not a contract" | ||
); | ||
require( | ||
Address.isContract(IBeacon(newBeacon).implementation()), | ||
"ERC1967: beacon implementation is not a contract" | ||
); | ||
StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; | ||
} | ||
|
||
/** | ||
* @dev Storage slot with the admin of the contract. | ||
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is | ||
* validated in the constructor. | ||
*/ | ||
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; | ||
|
||
/** | ||
* @dev Returns the current admin. | ||
*/ | ||
function _getAdmin() internal view returns (address) { | ||
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; | ||
} | ||
|
||
/** | ||
* @dev Stores a new address in the EIP1967 admin slot. | ||
*/ | ||
function _setAdmin(address newAdmin) internal { | ||
require(newAdmin != address(0), "ERC1967: new admin is the zero address"); | ||
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.3; | ||
|
||
import "./ERC1967Storage.sol"; | ||
|
||
/** | ||
* @dev This abstract contract provides event emitting update functions for | ||
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. | ||
* | ||
* @custom:oz-upgrades-unsafe-allow delegatecall | ||
*/ | ||
abstract contract ERC1967Upgrade is ERC1967Storage { | ||
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 | ||
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; | ||
|
||
/** | ||
* @dev Emitted when the implementation is upgraded. | ||
*/ | ||
event Upgraded(address indexed implementation); | ||
|
||
/** | ||
* @dev Emitted when the beacon is upgraded. | ||
*/ | ||
event BeaconUpgraded(address indexed beacon); | ||
|
||
/** | ||
* @dev Emitted when the admin account has changed. | ||
*/ | ||
event AdminChanged(address previousAdmin, address newAdmin); | ||
|
||
/** | ||
* @dev Perform implementation upgrade | ||
* | ||
* Emits an {Upgraded} event. | ||
*/ | ||
function _upgradeTo(address newImplementation) internal { | ||
_setImplementation(newImplementation); | ||
emit Upgraded(newImplementation); | ||
} | ||
|
||
/** | ||
* @dev Perform implementation upgrade with additional setup call. | ||
* | ||
* Emits an {Upgraded} event. | ||
*/ | ||
function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { | ||
_setImplementation(newImplementation); | ||
emit Upgraded(newImplementation); | ||
if (data.length > 0 || forceCall) { | ||
Address.functionDelegateCall(newImplementation, data); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. | ||
* | ||
* Emits an {Upgraded} event. | ||
*/ | ||
function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal { | ||
address oldImplementation = _getImplementation(); | ||
// do inital upgrade | ||
_setImplementation(newImplementation); | ||
// do setup call | ||
if (data.length > 0 || forceCall) { | ||
Address.functionDelegateCall(newImplementation, data); | ||
} | ||
// check if nested in an upgrade check | ||
StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT); | ||
if (!rollbackTesting.value) { | ||
// trigger upgrade check with flag set to true | ||
rollbackTesting.value = true; | ||
Address.functionDelegateCall( | ||
newImplementation, | ||
abi.encodeWithSignature( | ||
"upgradeTo(address)", | ||
oldImplementation | ||
) | ||
); | ||
rollbackTesting.value = false; | ||
// check upgrade was effective | ||
require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); | ||
// reset upgrade | ||
_setImplementation(newImplementation); | ||
// emit event | ||
emit Upgraded(newImplementation); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does | ||
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). | ||
* | ||
* Emits a {BeaconUpgraded} event. | ||
*/ | ||
function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { | ||
_setBeacon(newBeacon); | ||
emit BeaconUpgraded(newBeacon); | ||
if (data.length > 0 || forceCall) { | ||
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Changes the admin of the proxy. | ||
* | ||
* Emits an {AdminChanged} event. | ||
*/ | ||
function _changeAdmin(address newAdmin) internal { | ||
emit AdminChanged(_getAdmin(), newAdmin); | ||
_setAdmin(newAdmin); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.