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

Implement UUPS proxy (ERC1822) #2542

Merged
merged 55 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9a8457e
add uups implementation
Amxx Feb 23, 2021
069baf0
fix UUPSProxy and add tests
Amxx Feb 24, 2021
ea41a20
refactor uups: proxy upgradeable is now uupsproxy
Amxx Feb 26, 2021
9108acd
fix error message prefix
Amxx Mar 2, 2021
52fbb1e
moving utils for implementation AND admin slots to ERC1967Utils + Tra…
Amxx Mar 4, 2021
9777045
rename Proxiable → ERC1967Upgrade
Amxx Mar 4, 2021
de80b2d
used beforeUpgrade ifAdmin in transparentUpgradeableProxy
Amxx Mar 4, 2021
13c4568
refactor ERC1967Utils using the a StorageSlot library
Amxx Mar 6, 2021
130f7ed
Expand ERC1967Utils to support implementation, admin and beacon slots
Amxx Mar 6, 2021
b2b3b2a
test & evaluate gas cost of secure upgrades
Amxx Mar 8, 2021
41a2057
more tests for ERC1967upgrade
Amxx Mar 8, 2021
afb7af7
refactor ERC1967Upgrade with an added secure variant
Amxx Mar 8, 2021
cf8e25d
hardcode upgradePending
Amxx Mar 8, 2021
29fc4cc
remove unwanted event when upgrading
Amxx Mar 8, 2021
3a07fe0
refactor
Amxx Mar 9, 2021
2253d85
refactor proxy upgrades again
Amxx Mar 9, 2021
b204b3c
fix rebasing issues
Amxx Mar 10, 2021
10faeac
add contract/proxy/simple
Amxx Mar 11, 2021
1b7e21e
moving files accross
Amxx Mar 12, 2021
3a28a38
fix documentation and migration script
Amxx Mar 12, 2021
43172c6
add storage slot testing
Amxx Mar 12, 2021
482d344
improve uups testing
Amxx Mar 12, 2021
d3eb92d
document the UUPS design
Amxx Mar 16, 2021
faf0db2
proxy readme refactor
Amxx Mar 16, 2021
266cc52
add forceCall flag to some upgradeToAndCall functions
Amxx Mar 17, 2021
7595bc3
fix mocks
Amxx Mar 17, 2021
cb25009
fix secure upgrade pattern
Amxx Mar 18, 2021
bd64929
rename _beforeUpgrade to _authorizeUpgrade
Amxx Mar 26, 2021
107f093
rename Proxiable to UUPSUpgradeable
Amxx Mar 29, 2021
8df1fa7
remove TODO comment
frangio Apr 9, 2021
45ad38e
reformat StorageSlot
frangio Apr 9, 2021
9d9ba5c
use new syntax for the custom natspec
Amxx Apr 13, 2021
666392a
Apply suggestions from code review
Amxx Apr 13, 2021
5d3d91f
Change secure update mechanism and address comments from review
Amxx Apr 13, 2021
f5c7c39
add a test: using a proxy as an implementation must revert
Amxx Apr 14, 2021
36274b0
re-enable rollback-based upgrade protection
Amxx Apr 14, 2021
49fb747
Merge remote-tracking branch 'upstream/master' into feature/UUPS
Amxx Apr 14, 2021
d8c6aa9
document storageslot.sol
Amxx Apr 15, 2021
56977b4
document sotrage slot
Amxx Apr 15, 2021
58b9bfd
remove hardhat/console.sol import
frangio Apr 15, 2021
9a52dfb
fix typos and tweak wording
frangio Apr 15, 2021
7c77eda
add docs for each function in StorageSlot
frangio Apr 15, 2021
6613128
fix heading level
frangio Apr 15, 2021
00beb23
rename _beforeUpgrade in docs and fix typo
frangio Apr 15, 2021
f31c862
add BeaconProxy._setBeacon for backwards compat
frangio Apr 15, 2021
5f54669
use name of return variable
frangio Apr 15, 2021
3fb6c00
rename test variables
frangio Apr 16, 2021
6ffa861
remove commented lines
frangio Apr 16, 2021
3a39dee
remove unnecessary describe block
frangio Apr 16, 2021
18ee863
rename proxiable -> upgradeable
frangio Apr 16, 2021
446f803
clarify test description
frangio Apr 16, 2021
d763982
make state variable private
frangio Apr 16, 2021
6780f1e
improve docs in ERC1967Upgrade
frangio Apr 16, 2021
89371fb
test upgrade to non uups implementation
Amxx Apr 16, 2021
140ca9f
remove overloaded *AndCall*
Amxx Apr 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions contracts/mocks/StorageSlotMock.sol
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; }
}
32 changes: 32 additions & 0 deletions contracts/mocks/UUPS/TestInProd.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
Amxx marked this conversation as resolved.
Show resolved Hide resolved

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(""));
}

function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual override {
ERC1967Upgrade._upgradeToAndCall(newImplementation, data);
}
}

contract UUPSUpgradeableBrokenMock is UUPSUpgradeableMock {
function upgradeTo(address) external virtual override {
// pass
}

function upgradeToAndCall(address, bytes memory) external payable virtual override {
// pass
}

}
54 changes: 4 additions & 50 deletions contracts/proxy/ERC1967/ERC1967Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@
pragma solidity ^0.8.0;

import "../Proxy.sol";
import "../../utils/Address.sol";
import "./ERC1967Upgrade.sol";

/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*
* Upgradeability is only provided internally through {_upgradeTo}. For an externally upgradeable proxy see
* {TransparentUpgradeableProxy}.
*/
contract ERC1967Proxy is Proxy {
contract ERC1967Proxy is Proxy, ERC1967Upgrade {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
*
Expand All @@ -23,56 +20,13 @@ contract ERC1967Proxy is Proxy {
*/
constructor(address _logic, bytes memory _data) payable {
assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
_setImplementation(_logic);
if(_data.length > 0) {
Address.functionDelegateCall(_logic, _data);
}
_upgradeToAndCall(_logic, _data);
}

/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);

/**
* @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 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

/**
* @dev Returns the current implementation address.
*/
function _implementation() internal view virtual override returns (address impl) {
bytes32 slot = _IMPLEMENTATION_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
impl := sload(slot)
}
}

/**
* @dev Upgrades the proxy to a new implementation.
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal virtual {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}

/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967Proxy: new implementation is not a contract");

bytes32 slot = _IMPLEMENTATION_SLOT;

// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, newImplementation)
}
return ERC1967Storage._getImplementation();
}
}
85 changes: 85 additions & 0 deletions contracts/proxy/ERC1967/ERC1967Storage.sol
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;
}
}
125 changes: 125 additions & 0 deletions contracts/proxy/ERC1967/ERC1967Upgrade.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// 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);
Amxx marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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) internal {
_upgradeToAndCall(newImplementation, data, false);
}

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) internal {
_upgradeToAndCallSecure(newImplementation, data, false);
}

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) internal {
_upgradeBeaconToAndCall(newBeacon, data, false);
}

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);
}
}
27 changes: 26 additions & 1 deletion contracts/proxy/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@ The abstract {Proxy} contract implements the core delegation functionality. If t

{ERC1967Proxy} provides a simple, fully functioning, proxy. While this proxy is not by itself upgradeable, it includes an internal upgrade interface. For an upgrade interface exposed externally to an admin, we provide {TransparentUpgradeableProxy}. Both of these contracts use the storage slots specified in https://eips.ethereum.org/EIPS/eip-1967[EIP1967] to avoid clashes with the storage of the implementation contract behind the proxy.

An alternative upgradeability mechanism is provided in <<Beacon>>. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. In this pattern, the proxy contract doesn't hold the implementation address in storage like {UpgradeableProxy}, but the address of a {UpgradeableBeacon} contract, which is where the implementation address is actually stored and retrieved from. The `upgrade` operations that change the implementation contract address are then sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded.
An alternative upgradeability mechanism is provided in <<Beacon>>. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. In this pattern, the proxy contract doesn't hold the implementation address in storage like {ERC1967Proxy}, but the address of a {UpgradeableBeacon} contract, which is where the implementation address is actually stored and retrieved from. The `upgrade` operations that change the implementation contract address are then sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded.

The {Clones} library provides a way to deploy minimal non-upgradeable proxies for cheap. This can be useful for applications that require deploying many instances of the same contract (for example one per user, or one per task). These instances are designed to be both cheap to deploy, and cheap to call. The drawback being that they are not upgradeable.

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.

== UUPS Design: Upgradeability as an Implementation Feature

Upgradeable smart contracts rely on proxies to relay the calls in a way that is programmable. As discussed previously, we provide different proxy contracts that each come with a specific set of features. Other designs, not (yet) proposed as part of the OpenZeppelin products, also exist.

The most simple, and common, design is known as Universal Upgradeable Proxy Standard (UUPS). This design is both lightweight and versatile and is proposed through the {ERC1967Proxy} and the {UUPSUpgradeable} contract.

While {UUPSUpgradeable} uses the same interface as {TransparentUpgradeableProxy}, in the first case the upgrade is handled by the implementation, and can eventually be removed. {TransparentUpgradeableProxy}, on the other hand, includes the upgrade logic in the proxy. This means {TransparentUpgradeableProxy} is more expensive to deploy. Note that, since both proxies use the same storage slot for the implementation address, using a UUPS compliant implementation with a {TransparentUpgradeableProxy} might allow non-admins to perform upgrade operations.

According to this design, the {ERC1967Proxy} is only capable of forwarding calls to an implementation contract. Unlike the more complex and expensive to deploy {TransparentUpgradeableProxy}, the {ERC1967Proxy} doesn't by itself provide any upgradeability mechanism. It is the role of the implementation to include, alongside the contract's logic, all the code necessary to update the implementation's address that is stored at a specific slot in the proxy's storage space. This is where the {UUPSUpgradeable} contract comes in. Inheriting from it (and overriding the {_authorizeUpgrade} function with the relevant access control mechanism) will turn your contract into a UUPS complaint implementation.

By default, the upgrade mechanism included in {UUPSUpgradeable} contains a security mechanism that will prevent any upgrades to a non UUPS compliant implementation. This prevents upgrades to an implementation contract that wouldn't contain the necessary upgrade mechanism, as it would lock the proxy forever. This security mechanism can however be bypassed, either by:

- 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 upgrade again to another implementation without the upgrade mechanism.

When doing an upgrade, the second parameter of the {upgradeToAndCall} function allows for the atomic execution of an optional initialization/migration call.

== Core

{{Proxy}}
Expand All @@ -23,6 +40,14 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th

{{ERC1967Proxy}}

{{ERC1967Storage}}

{{ERC1967Upgrade}}

== UUPS

{{UUPSUpgradeable}}

== Transparent Proxy

{{TransparentUpgradeableProxy}}
Expand Down
Loading