Simplify UUPSUpgradeable along the lines of ERC1822 (#3021)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
This commit is contained in:
@ -4,6 +4,7 @@
|
||||
pragma solidity ^0.8.2;
|
||||
|
||||
import "../beacon/IBeacon.sol";
|
||||
import "../../interfaces/draft-IERC1822.sol";
|
||||
import "../../utils/Address.sol";
|
||||
import "../../utils/StorageSlot.sol";
|
||||
|
||||
@ -77,33 +78,23 @@ abstract contract ERC1967Upgrade {
|
||||
*
|
||||
* Emits an {Upgraded} event.
|
||||
*/
|
||||
function _upgradeToAndCallSecure(
|
||||
function _upgradeToAndCallUUPS(
|
||||
address newImplementation,
|
||||
bytes memory data,
|
||||
bool forceCall
|
||||
) internal {
|
||||
address oldImplementation = _getImplementation();
|
||||
|
||||
// Initial upgrade and setup call
|
||||
_setImplementation(newImplementation);
|
||||
if (data.length > 0 || forceCall) {
|
||||
Address.functionDelegateCall(newImplementation, data);
|
||||
}
|
||||
|
||||
// Perform rollback test if not already in progress
|
||||
StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
|
||||
if (!rollbackTesting.value) {
|
||||
// Trigger rollback using upgradeTo from the new implementation
|
||||
rollbackTesting.value = true;
|
||||
Address.functionDelegateCall(
|
||||
newImplementation,
|
||||
abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
|
||||
);
|
||||
rollbackTesting.value = false;
|
||||
// Check rollback was effective
|
||||
require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
|
||||
// Finally reset to the new implementation and log the upgrade
|
||||
_upgradeTo(newImplementation);
|
||||
// Upgrades from old implementations will perform a rollback test. This test requires the new
|
||||
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
|
||||
// this special case will break upgrade paths from old UUPS implementation to new ones.
|
||||
if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
|
||||
_setImplementation(newImplementation);
|
||||
} else {
|
||||
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
|
||||
require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
|
||||
} catch {
|
||||
revert("ERC1967Upgrade: new implementation is not UUPS");
|
||||
}
|
||||
_upgradeToAndCall(newImplementation, data, forceCall);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,14 +17,14 @@ In order to avoid clashes with the storage variables of the implementation contr
|
||||
There are two alternative ways to add upgradeability to an ERC1967 proxy. Their differences are explained below in <<transparent-vs-uups>>.
|
||||
|
||||
- {TransparentUpgradeableProxy}: A proxy with a built in admin and upgrade interface.
|
||||
- {UUPSUpgradeable}: An upgradeability mechanism to be included in the implementation for an ERC1967 proxy.
|
||||
- {UUPSUpgradeable}: An upgradeability mechanism to be included in the implementation contract.
|
||||
|
||||
CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Hardhat.
|
||||
|
||||
A different family of proxies are beacon proxies. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction.
|
||||
|
||||
- {BeaconProxy}: A proxy that retreives its implementation from a beacon contract.
|
||||
- {UpgradeableBeacon}: A beacon contract that can be upgraded.
|
||||
- {UpgradeableBeacon}: A beacon contract with a built in admin that can upgrade the {BeaconProxy} pointing to it.
|
||||
|
||||
In this pattern, the proxy contract doesn't hold the implementation address in storage like an ERC1967 proxy, instead the address is stored in a separate beacon contract. The `upgrade` operations that are sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded.
|
||||
|
||||
@ -48,6 +48,8 @@ By default, the upgrade functionality included in {UUPSUpgradeable} contains a s
|
||||
- Adding a flag mechanism in the implementation that will disable the upgrade function when triggered.
|
||||
- Upgrading to an implementation that features an upgrade mechanism without the additional security check, and then upgrading again to another implementation without the upgrade mechanism.
|
||||
|
||||
The current implementation of this security mechanism uses https://eips.ethereum.org/EIPS/eip-1822[EIP1822] to detect the storage slot used by the implementation. A previous implementation, now deprecated, relied on a rollback check. It is possible to upgrade from a contract using the old mechanism to a new one. The inverse is however not possible, as old implementations (before version 4.5) did not include the `ERC1822` interface.
|
||||
|
||||
== Core
|
||||
|
||||
{{Proxy}}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../../interfaces/draft-IERC1822.sol";
|
||||
import "../ERC1967/ERC1967Upgrade.sol";
|
||||
|
||||
/**
|
||||
@ -17,7 +18,7 @@ import "../ERC1967/ERC1967Upgrade.sol";
|
||||
*
|
||||
* _Available since v4.1._
|
||||
*/
|
||||
abstract contract UUPSUpgradeable is ERC1967Upgrade {
|
||||
abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade {
|
||||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
|
||||
address private immutable __self = address(this);
|
||||
|
||||
@ -34,6 +35,27 @@ abstract contract UUPSUpgradeable is ERC1967Upgrade {
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
|
||||
* callable on the implementing contract but not through proxies.
|
||||
*/
|
||||
modifier notDelegated() {
|
||||
require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
|
||||
* implementation. It is used to validate that the this implementation remains valid after an upgrade.
|
||||
*
|
||||
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
|
||||
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
|
||||
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
|
||||
*/
|
||||
function proxiableUUID() external view virtual override notDelegated returns (bytes32) {
|
||||
return _IMPLEMENTATION_SLOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Upgrade the implementation of the proxy to `newImplementation`.
|
||||
*
|
||||
@ -43,7 +65,7 @@ abstract contract UUPSUpgradeable is ERC1967Upgrade {
|
||||
*/
|
||||
function upgradeTo(address newImplementation) external virtual onlyProxy {
|
||||
_authorizeUpgrade(newImplementation);
|
||||
_upgradeToAndCallSecure(newImplementation, new bytes(0), false);
|
||||
_upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +78,7 @@ abstract contract UUPSUpgradeable is ERC1967Upgrade {
|
||||
*/
|
||||
function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy {
|
||||
_authorizeUpgrade(newImplementation);
|
||||
_upgradeToAndCallSecure(newImplementation, data, true);
|
||||
_upgradeToAndCallUUPS(newImplementation, data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user