Add Proxies from OpenZeppelin SDK (#2335)
This commit is contained in:
committed by
GitHub
parent
0b489f4d79
commit
cb791a1b21
62
contracts/proxy/Initializable.sol
Normal file
62
contracts/proxy/Initializable.sol
Normal file
@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity >=0.4.24 <0.7.0;
|
||||
|
||||
|
||||
/**
|
||||
* @title Initializable
|
||||
*
|
||||
* @dev Helper contract to support initializer functions. To use it, replace
|
||||
* the constructor with a function that has the `initializer` modifier.
|
||||
* WARNING: Unlike constructors, initializer functions must be manually
|
||||
* invoked. This applies both to deploying an Initializable contract, as well
|
||||
* as extending an Initializable contract via inheritance.
|
||||
* WARNING: When used with inheritance, manual care must be taken to not invoke
|
||||
* a parent initializer twice, or ensure that all initializers are idempotent,
|
||||
* because this is not dealt with automatically as with constructors.
|
||||
*/
|
||||
contract Initializable {
|
||||
|
||||
/**
|
||||
* @dev Indicates that the contract has been initialized.
|
||||
*/
|
||||
bool private _initialized;
|
||||
|
||||
/**
|
||||
* @dev Indicates that the contract is in the process of being initialized.
|
||||
*/
|
||||
bool private _initializing;
|
||||
|
||||
/**
|
||||
* @dev Modifier to use in the initializer function of a contract.
|
||||
*/
|
||||
modifier initializer() {
|
||||
require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized");
|
||||
|
||||
bool isTopLevelCall = !_initializing;
|
||||
if (isTopLevelCall) {
|
||||
_initializing = true;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
_;
|
||||
|
||||
if (isTopLevelCall) {
|
||||
_initializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Returns true if and only if the function is running in the constructor
|
||||
function _isConstructor() private view returns (bool) {
|
||||
// extcodesize checks the size of the code stored in an address, and
|
||||
// address returns the current address. Since the code is still not
|
||||
// deployed when running a constructor, any checks on its code size will
|
||||
// yield zero, making it an effective way to detect if a contract is
|
||||
// under construction or not.
|
||||
address self = address(this);
|
||||
uint256 cs;
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly { cs := extcodesize(self) }
|
||||
return cs == 0;
|
||||
}
|
||||
}
|
||||
78
contracts/proxy/Proxy.sol
Normal file
78
contracts/proxy/Proxy.sol
Normal file
@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
/**
|
||||
* @title Proxy
|
||||
* @dev Implements delegation of calls to other contracts, with proper
|
||||
* forwarding of return values and bubbling of failures.
|
||||
* It defines a fallback function that delegates all calls to the address
|
||||
* returned by the abstract _implementation() internal function.
|
||||
*/
|
||||
abstract contract Proxy {
|
||||
/**
|
||||
* @dev Fallback function.
|
||||
* Implemented entirely in `_fallback`.
|
||||
*/
|
||||
fallback () payable external {
|
||||
_fallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Receive function.
|
||||
* Implemented entirely in `_fallback`.
|
||||
*/
|
||||
receive () payable external {
|
||||
_fallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Address of the implementation.
|
||||
*/
|
||||
function _implementation() internal virtual view returns (address);
|
||||
|
||||
/**
|
||||
* @dev Delegates execution to an implementation contract.
|
||||
* This is a low level function that doesn't return to its internal call site.
|
||||
* It will return to the external caller whatever the implementation returns.
|
||||
* @param implementation Address to delegate.
|
||||
*/
|
||||
function _delegate(address implementation) internal {
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
// Copy msg.data. We take full control of memory in this inline assembly
|
||||
// block because it will not return to Solidity code. We overwrite the
|
||||
// Solidity scratch pad at memory position 0.
|
||||
calldatacopy(0, 0, calldatasize())
|
||||
|
||||
// Call the implementation.
|
||||
// out and outsize are 0 because we don't know the size yet.
|
||||
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
|
||||
|
||||
// Copy the returned data.
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
|
||||
switch result
|
||||
// delegatecall returns 0 on error.
|
||||
case 0 { revert(0, returndatasize()) }
|
||||
default { return(0, returndatasize()) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function that is run as the first thing in the fallback function.
|
||||
* Can be redefined in derived contracts to add functionality.
|
||||
* Redefinitions must call super._willFallback().
|
||||
*/
|
||||
function _willFallback() internal virtual {
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev fallback implementation.
|
||||
* Extracted to enable manual triggering.
|
||||
*/
|
||||
function _fallback() internal {
|
||||
_willFallback();
|
||||
_delegate(_implementation());
|
||||
}
|
||||
}
|
||||
70
contracts/proxy/ProxyAdmin.sol
Normal file
70
contracts/proxy/ProxyAdmin.sol
Normal file
@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "../access/Ownable.sol";
|
||||
import "./TransparentUpgradeableProxy.sol";
|
||||
|
||||
/**
|
||||
* @title ProxyAdmin
|
||||
* @dev This contract is the admin of a proxy, and is in charge
|
||||
* of upgrading it as well as transferring it to another admin.
|
||||
*/
|
||||
contract ProxyAdmin is Ownable {
|
||||
|
||||
/**
|
||||
* @dev Returns the current implementation of a proxy.
|
||||
* This is needed because only the proxy admin can query it.
|
||||
* @return The address of the current implementation of the proxy.
|
||||
*/
|
||||
function getProxyImplementation(TransparentUpgradeableProxy proxy) public view returns (address) {
|
||||
// We need to manually run the static call since the getter cannot be flagged as view
|
||||
// bytes4(keccak256("implementation()")) == 0x5c60da1b
|
||||
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
|
||||
require(success);
|
||||
return abi.decode(returndata, (address));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the admin of a proxy. Only the admin can query it.
|
||||
* @return The address of the current admin of the proxy.
|
||||
*/
|
||||
function getProxyAdmin(TransparentUpgradeableProxy proxy) public view returns (address) {
|
||||
// We need to manually run the static call since the getter cannot be flagged as view
|
||||
// bytes4(keccak256("admin()")) == 0xf851a440
|
||||
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
|
||||
require(success);
|
||||
return abi.decode(returndata, (address));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Changes the admin of a proxy.
|
||||
* @param proxy Proxy to change admin.
|
||||
* @param newAdmin Address to transfer proxy administration to.
|
||||
*/
|
||||
function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public onlyOwner {
|
||||
proxy.changeAdmin(newAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Upgrades a proxy to the newest implementation of a contract.
|
||||
* @param proxy Proxy to be upgraded.
|
||||
* @param implementation the address of the Implementation.
|
||||
*/
|
||||
function upgrade(TransparentUpgradeableProxy proxy, address implementation) public onlyOwner {
|
||||
proxy.upgradeTo(implementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Upgrades a proxy to the newest implementation of a contract and forwards a function call to it.
|
||||
* This is useful to initialize the proxied contract.
|
||||
* @param proxy Proxy to be upgraded.
|
||||
* @param implementation Address of the Implementation.
|
||||
* @param data Data to send as msg.data in the low level call.
|
||||
* It should include the signature and the parameters of the function to be called, as described in
|
||||
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
|
||||
*/
|
||||
function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable onlyOwner {
|
||||
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
|
||||
}
|
||||
}
|
||||
139
contracts/proxy/TransparentUpgradeableProxy.sol
Normal file
139
contracts/proxy/TransparentUpgradeableProxy.sol
Normal file
@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "./UpgradeableProxy.sol";
|
||||
|
||||
/**
|
||||
* @title TransparentUpgradeableProxy
|
||||
* @dev This contract combines an upgradeability proxy with an authorization
|
||||
* mechanism for administrative tasks.
|
||||
* All external functions in this contract must be guarded by the
|
||||
* `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
|
||||
* feature proposal that would enable this to be done automatically.
|
||||
*/
|
||||
contract TransparentUpgradeableProxy is UpgradeableProxy {
|
||||
/**
|
||||
* Contract constructor.
|
||||
* @param _logic address of the initial implementation.
|
||||
* @param _admin Address of the proxy administrator.
|
||||
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
|
||||
* It should include the signature and the parameters of the function to be called, as described in
|
||||
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
|
||||
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
|
||||
*/
|
||||
constructor(address _logic, address _admin, bytes memory _data) public payable UpgradeableProxy(_logic, _data) {
|
||||
assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
|
||||
_setAdmin(_admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emitted when the administration has been transferred.
|
||||
* @param previousAdmin Address of the previous admin.
|
||||
* @param newAdmin Address of the new admin.
|
||||
*/
|
||||
event AdminChanged(address previousAdmin, address newAdmin);
|
||||
|
||||
/**
|
||||
* @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 private constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
|
||||
|
||||
/**
|
||||
* @dev Modifier to check whether the `msg.sender` is the admin.
|
||||
* If it is, it will run the function. Otherwise, it will delegate the call
|
||||
* to the implementation.
|
||||
*/
|
||||
modifier ifAdmin() {
|
||||
if (msg.sender == _admin()) {
|
||||
_;
|
||||
} else {
|
||||
_fallback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The address of the proxy admin.
|
||||
*/
|
||||
function admin() external ifAdmin returns (address) {
|
||||
return _admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The address of the implementation.
|
||||
*/
|
||||
function implementation() external ifAdmin returns (address) {
|
||||
return _implementation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Changes the admin of the proxy.
|
||||
* Only the current admin can call this function.
|
||||
* @param newAdmin Address to transfer proxy administration to.
|
||||
*/
|
||||
function changeAdmin(address newAdmin) external ifAdmin {
|
||||
require(newAdmin != address(0), "TransparentUpgradeableProxy: new admin is the zero address");
|
||||
emit AdminChanged(_admin(), newAdmin);
|
||||
_setAdmin(newAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Upgrade the backing implementation of the proxy.
|
||||
* Only the admin can call this function.
|
||||
* @param newImplementation Address of the new implementation.
|
||||
*/
|
||||
function upgradeTo(address newImplementation) external ifAdmin {
|
||||
_upgradeTo(newImplementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Upgrade the backing implementation of the proxy and call a function
|
||||
* on the new implementation.
|
||||
* This is useful to initialize the proxied contract.
|
||||
* @param newImplementation Address of the new implementation.
|
||||
* @param data Data to send as msg.data in the low level call.
|
||||
* It should include the signature and the parameters of the function to be called, as described in
|
||||
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
|
||||
*/
|
||||
function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
|
||||
_upgradeTo(newImplementation);
|
||||
// solhint-disable-next-line avoid-low-level-calls
|
||||
(bool success,) = newImplementation.delegatecall(data);
|
||||
require(success);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return adm The admin slot.
|
||||
*/
|
||||
function _admin() internal view returns (address adm) {
|
||||
bytes32 slot = _ADMIN_SLOT;
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
adm := sload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the address of the proxy admin.
|
||||
* @param newAdmin Address of the new proxy admin.
|
||||
*/
|
||||
function _setAdmin(address newAdmin) internal {
|
||||
bytes32 slot = _ADMIN_SLOT;
|
||||
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
sstore(slot, newAdmin)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Only fallback when the sender is not the admin.
|
||||
*/
|
||||
function _willFallback() internal override virtual {
|
||||
require(msg.sender != _admin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
|
||||
super._willFallback();
|
||||
}
|
||||
}
|
||||
81
contracts/proxy/UpgradeableProxy.sol
Normal file
81
contracts/proxy/UpgradeableProxy.sol
Normal file
@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "./Proxy.sol";
|
||||
import "../utils/Address.sol";
|
||||
|
||||
/**
|
||||
* @title UpgradeableProxy
|
||||
* @dev This contract implements a proxy that allows to change the
|
||||
* implementation address to which it will delegate.
|
||||
* Such a change is called an implementation upgrade.
|
||||
*/
|
||||
contract UpgradeableProxy is Proxy {
|
||||
/**
|
||||
* @dev Contract constructor.
|
||||
* @param _logic Address of the initial implementation.
|
||||
* @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
|
||||
* It should include the signature and the parameters of the function to be called, as described in
|
||||
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
|
||||
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
|
||||
*/
|
||||
constructor(address _logic, bytes memory _data) public payable {
|
||||
assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
|
||||
_setImplementation(_logic);
|
||||
if(_data.length > 0) {
|
||||
// solhint-disable-next-line avoid-low-level-calls
|
||||
(bool success,) = _logic.delegatecall(_data);
|
||||
require(success);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emitted when the implementation is upgraded.
|
||||
* @param implementation Address of the new implementation.
|
||||
*/
|
||||
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.
|
||||
* @return impl Address of the current implementation
|
||||
*/
|
||||
function _implementation() internal override view 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.
|
||||
* @param newImplementation Address of the new implementation.
|
||||
*/
|
||||
function _upgradeTo(address newImplementation) internal {
|
||||
_setImplementation(newImplementation);
|
||||
emit Upgraded(newImplementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the implementation address of the proxy.
|
||||
* @param newImplementation Address of the new implementation.
|
||||
*/
|
||||
function _setImplementation(address newImplementation) internal {
|
||||
require(Address.isContract(newImplementation), "UpgradeableProxy: new implementation is not a contract");
|
||||
|
||||
bytes32 slot = _IMPLEMENTATION_SLOT;
|
||||
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
sstore(slot, newImplementation)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user