Add Proxies from OpenZeppelin SDK (#2335)
This commit is contained in:
committed by
GitHub
parent
0b489f4d79
commit
cb791a1b21
20
contracts/mocks/ClashingImplementation.sol
Normal file
20
contracts/mocks/ClashingImplementation.sol
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Implementation contract with an admin() function made to clash with
|
||||||
|
* @dev TransparentUpgradeableProxy's to test correct functioning of the
|
||||||
|
* @dev Transparent Proxy feature.
|
||||||
|
*/
|
||||||
|
contract ClashingImplementation {
|
||||||
|
|
||||||
|
function admin() external pure returns (address) {
|
||||||
|
return 0x0000000000000000000000000000000011111142;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delegatedFunction() external pure returns (bool) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
contracts/mocks/DummyImplementation.sol
Normal file
57
contracts/mocks/DummyImplementation.sol
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
abstract contract Impl {
|
||||||
|
function version() public pure virtual returns (string memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
contract DummyImplementation {
|
||||||
|
uint256 public value;
|
||||||
|
string public text;
|
||||||
|
uint256[] public values;
|
||||||
|
|
||||||
|
function initializeNonPayable() public {
|
||||||
|
value = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializePayable() payable public {
|
||||||
|
value = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeNonPayable(uint256 _value) public {
|
||||||
|
value = _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializePayable(uint256 _value) payable public {
|
||||||
|
value = _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize(uint256 _value, string memory _text, uint256[] memory _values) public {
|
||||||
|
value = _value;
|
||||||
|
text = _text;
|
||||||
|
values = _values;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get() public pure returns (bool) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function version() public pure virtual returns (string memory) {
|
||||||
|
return "V1";
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverts() public pure {
|
||||||
|
require(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract DummyImplementationV2 is DummyImplementation {
|
||||||
|
function migrate(uint256 newVal) payable public {
|
||||||
|
value = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function version() public pure override returns (string memory) {
|
||||||
|
return "V2";
|
||||||
|
}
|
||||||
|
}
|
||||||
36
contracts/mocks/InitializableMock.sol
Normal file
36
contracts/mocks/InitializableMock.sol
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
import "../proxy/Initializable.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title InitializableMock
|
||||||
|
* @dev This contract is a mock to test initializable functionality
|
||||||
|
*/
|
||||||
|
contract InitializableMock is Initializable {
|
||||||
|
|
||||||
|
bool public initializerRan;
|
||||||
|
uint256 public x;
|
||||||
|
|
||||||
|
function initialize() public initializer {
|
||||||
|
initializerRan = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeNested() public initializer {
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeWithX(uint256 _x) public payable initializer {
|
||||||
|
x = _x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nonInitializable(uint256 _x) public payable {
|
||||||
|
x = _x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail() public pure {
|
||||||
|
require(false, "InitializableMock forced failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
76
contracts/mocks/MultipleInheritanceInitializableMocks.sol
Normal file
76
contracts/mocks/MultipleInheritanceInitializableMocks.sol
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
import "../proxy/Initializable.sol";
|
||||||
|
|
||||||
|
// Sample contracts showing upgradeability with multiple inheritance.
|
||||||
|
// Child contract inherits from Father and Mother contracts, and Father extends from Gramps.
|
||||||
|
//
|
||||||
|
// Human
|
||||||
|
// / \
|
||||||
|
// | Gramps
|
||||||
|
// | |
|
||||||
|
// Mother Father
|
||||||
|
// | |
|
||||||
|
// -- Child --
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample base intializable contract that is a human
|
||||||
|
*/
|
||||||
|
contract SampleHuman is Initializable {
|
||||||
|
bool public isHuman;
|
||||||
|
|
||||||
|
function initialize() public initializer {
|
||||||
|
isHuman = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample base intializable contract that defines a field mother
|
||||||
|
*/
|
||||||
|
contract SampleMother is Initializable, SampleHuman {
|
||||||
|
uint256 public mother;
|
||||||
|
|
||||||
|
function initialize(uint256 value) public initializer virtual {
|
||||||
|
SampleHuman.initialize();
|
||||||
|
mother = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample base intializable contract that defines a field gramps
|
||||||
|
*/
|
||||||
|
contract SampleGramps is Initializable, SampleHuman {
|
||||||
|
string public gramps;
|
||||||
|
|
||||||
|
function initialize(string memory value) public initializer virtual {
|
||||||
|
SampleHuman.initialize();
|
||||||
|
gramps = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample base intializable contract that defines a field father and extends from gramps
|
||||||
|
*/
|
||||||
|
contract SampleFather is Initializable, SampleGramps {
|
||||||
|
uint256 public father;
|
||||||
|
|
||||||
|
function initialize(string memory _gramps, uint256 _father) public initializer {
|
||||||
|
SampleGramps.initialize(_gramps);
|
||||||
|
father = _father;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Child extends from mother, father (gramps)
|
||||||
|
*/
|
||||||
|
contract SampleChild is Initializable, SampleMother, SampleFather {
|
||||||
|
uint256 public child;
|
||||||
|
|
||||||
|
function initialize(uint256 _mother, string memory _gramps, uint256 _father, uint256 _child) public initializer {
|
||||||
|
SampleMother.initialize(_mother);
|
||||||
|
SampleFather.initialize(_gramps, _father);
|
||||||
|
child = _child;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
contracts/mocks/RegressionImplementation.sol
Normal file
66
contracts/mocks/RegressionImplementation.sol
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
import "../proxy/Initializable.sol";
|
||||||
|
|
||||||
|
contract Implementation1 is Initializable {
|
||||||
|
uint internal _value;
|
||||||
|
|
||||||
|
function initialize() public initializer {
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(uint _number) public {
|
||||||
|
_value = _number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract Implementation2 is Initializable {
|
||||||
|
uint internal _value;
|
||||||
|
|
||||||
|
function initialize() public initializer {
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(uint _number) public {
|
||||||
|
_value = _number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue() public view returns (uint) {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract Implementation3 is Initializable {
|
||||||
|
uint internal _value;
|
||||||
|
|
||||||
|
function initialize() public initializer {
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(uint _number) public {
|
||||||
|
_value = _number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(uint _number) public view returns (uint) {
|
||||||
|
return _value + _number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract Implementation4 is Initializable {
|
||||||
|
uint internal _value;
|
||||||
|
|
||||||
|
function initialize() public initializer {
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(uint _number) public {
|
||||||
|
_value = _number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue() public view returns (uint) {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// solhint-disable-next-line payable-fallback
|
||||||
|
fallback() external {
|
||||||
|
_value = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
contracts/mocks/SingleInheritanceInitializableMocks.sol
Normal file
49
contracts/mocks/SingleInheritanceInitializableMocks.sol
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.0;
|
||||||
|
|
||||||
|
import "../proxy/Initializable.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title MigratableMockV1
|
||||||
|
* @dev This contract is a mock to test initializable functionality through migrations
|
||||||
|
*/
|
||||||
|
contract MigratableMockV1 is Initializable {
|
||||||
|
uint256 public x;
|
||||||
|
|
||||||
|
function initialize(uint256 value) public payable initializer {
|
||||||
|
x = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title MigratableMockV2
|
||||||
|
* @dev This contract is a mock to test migratable functionality with params
|
||||||
|
*/
|
||||||
|
contract MigratableMockV2 is MigratableMockV1 {
|
||||||
|
bool internal _migratedV2;
|
||||||
|
uint256 public y;
|
||||||
|
|
||||||
|
function migrate(uint256 value, uint256 anotherValue) public payable {
|
||||||
|
require(!_migratedV2);
|
||||||
|
x = value;
|
||||||
|
y = anotherValue;
|
||||||
|
_migratedV2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title MigratableMockV3
|
||||||
|
* @dev This contract is a mock to test migratable functionality without params
|
||||||
|
*/
|
||||||
|
contract MigratableMockV3 is MigratableMockV2 {
|
||||||
|
bool internal _migratedV3;
|
||||||
|
|
||||||
|
function migrate() public payable {
|
||||||
|
require(!_migratedV3);
|
||||||
|
uint256 oldX = x;
|
||||||
|
x = y;
|
||||||
|
y = oldX;
|
||||||
|
_migratedV3 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
test/proxy/Initializable.test.js
Normal file
81
test/proxy/Initializable.test.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
const { contract } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||||
|
|
||||||
|
const { assert } = require('chai');
|
||||||
|
|
||||||
|
const InitializableMock = contract.fromArtifact('InitializableMock');
|
||||||
|
const SampleChild = contract.fromArtifact('SampleChild');
|
||||||
|
|
||||||
|
describe('Initializable', function () {
|
||||||
|
describe('basic testing without inheritance', function () {
|
||||||
|
beforeEach('deploying', async function () {
|
||||||
|
this.contract = await InitializableMock.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
context('before initialize', function () {
|
||||||
|
it('initializer has not run', async function () {
|
||||||
|
assert.isFalse(await this.contract.initializerRan());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('after initialize', function () {
|
||||||
|
beforeEach('initializing', async function () {
|
||||||
|
await this.contract.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializer has run', async function () {
|
||||||
|
assert.isTrue(await this.contract.initializerRan());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializer does not run again', async function () {
|
||||||
|
await expectRevert(this.contract.initialize(), 'Initializable: contract is already initialized');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('after nested initialize', function () {
|
||||||
|
beforeEach('initializing', async function () {
|
||||||
|
await this.contract.initializeNested();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializer has run', async function () {
|
||||||
|
assert.isTrue(await this.contract.initializerRan());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('complex testing with inheritance', function () {
|
||||||
|
const mother = 12;
|
||||||
|
const gramps = '56';
|
||||||
|
const father = 34;
|
||||||
|
const child = 78;
|
||||||
|
|
||||||
|
beforeEach('deploying', async function () {
|
||||||
|
this.contract = await SampleChild.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach('initializing', async function () {
|
||||||
|
await this.contract.initialize(mother, gramps, father, child);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes human', async function () {
|
||||||
|
assert.equal(await this.contract.isHuman(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes mother', async function () {
|
||||||
|
assert.equal(await this.contract.mother(), mother);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes gramps', async function () {
|
||||||
|
assert.equal(await this.contract.gramps(), gramps);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes father', async function () {
|
||||||
|
assert.equal(await this.contract.father(), father);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes child', async function () {
|
||||||
|
assert.equal(await this.contract.child(), child);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
119
test/proxy/ProxyAdmin.test.js
Normal file
119
test/proxy/ProxyAdmin.test.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const { accounts, contract } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||||
|
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
const ImplV1 = contract.fromArtifact('DummyImplementation');
|
||||||
|
const ImplV2 = contract.fromArtifact('DummyImplementationV2');
|
||||||
|
const ProxyAdmin = contract.fromArtifact('ProxyAdmin');
|
||||||
|
const TransparentUpgradeableProxy = contract.fromArtifact('TransparentUpgradeableProxy');
|
||||||
|
|
||||||
|
describe('ProxyAdmin', function () {
|
||||||
|
const [proxyAdminOwner, newAdmin, anotherAccount] = accounts;
|
||||||
|
|
||||||
|
before('set implementations', async function () {
|
||||||
|
this.implementationV1 = await ImplV1.new();
|
||||||
|
this.implementationV2 = await ImplV2.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
const initializeData = Buffer.from('');
|
||||||
|
this.proxyAdmin = await ProxyAdmin.new({ from: proxyAdminOwner });
|
||||||
|
this.proxy = await TransparentUpgradeableProxy.new(
|
||||||
|
this.implementationV1.address,
|
||||||
|
this.proxyAdmin.address,
|
||||||
|
initializeData,
|
||||||
|
{ from: proxyAdminOwner },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has an owner', async function () {
|
||||||
|
expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getProxyAdmin', function () {
|
||||||
|
it('returns proxyAdmin as admin of the proxy', async function () {
|
||||||
|
const admin = await this.proxyAdmin.getProxyAdmin(this.proxy.address);
|
||||||
|
expect(admin).to.be.equal(this.proxyAdmin.address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#changeProxyAdmin', function () {
|
||||||
|
it('fails to change proxy admin if its not the proxy owner', async function () {
|
||||||
|
await expectRevert(
|
||||||
|
this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: anotherAccount }),
|
||||||
|
'caller is not the owner',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes proxy admin', async function () {
|
||||||
|
await this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: proxyAdminOwner });
|
||||||
|
expect(await this.proxy.admin.call({ from: newAdmin })).to.eq(newAdmin);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getProxyImplementation', function () {
|
||||||
|
it('returns proxy implementation address', async function () {
|
||||||
|
const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
|
||||||
|
expect(implementationAddress).to.be.equal(this.implementationV1.address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#upgrade', function () {
|
||||||
|
context('with unauthorized account', function () {
|
||||||
|
it('fails to upgrade', async function () {
|
||||||
|
await expectRevert(
|
||||||
|
this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: anotherAccount }),
|
||||||
|
'caller is not the owner',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with authorized account', function () {
|
||||||
|
it('upgrades implementation', async function () {
|
||||||
|
await this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: proxyAdminOwner });
|
||||||
|
const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
|
||||||
|
expect(implementationAddress).to.be.equal(this.implementationV2.address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#upgradeAndCall', function () {
|
||||||
|
context('with unauthorized account', function () {
|
||||||
|
it('fails to upgrade', async function () {
|
||||||
|
const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI();
|
||||||
|
await expectRevert(
|
||||||
|
this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
|
||||||
|
{ from: anotherAccount }
|
||||||
|
),
|
||||||
|
'caller is not the owner',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with authorized account', function () {
|
||||||
|
context('with invalid callData', function () {
|
||||||
|
it('fails to upgrade', async function () {
|
||||||
|
const callData = '0x12345678';
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
|
||||||
|
{ from: proxyAdminOwner }
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with valid callData', function () {
|
||||||
|
it('upgrades implementation', async function () {
|
||||||
|
const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI();
|
||||||
|
await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
|
||||||
|
{ from: proxyAdminOwner }
|
||||||
|
);
|
||||||
|
const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
|
||||||
|
expect(implementationAddress).to.be.equal(this.implementationV2.address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
436
test/proxy/TransparentUpgradeableProxy.behaviour.js
Normal file
436
test/proxy/TransparentUpgradeableProxy.behaviour.js
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
const { contract, web3 } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const { BN, expectRevert, expectEvent, constants } = require('@openzeppelin/test-helpers');
|
||||||
|
const { ZERO_ADDRESS } = constants;
|
||||||
|
const { toChecksumAddress, keccak256 } = require('ethereumjs-util');
|
||||||
|
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
const Proxy = contract.fromArtifact('Proxy');
|
||||||
|
const Implementation1 = contract.fromArtifact('Implementation1');
|
||||||
|
const Implementation2 = contract.fromArtifact('Implementation2');
|
||||||
|
const Implementation3 = contract.fromArtifact('Implementation3');
|
||||||
|
const Implementation4 = contract.fromArtifact('Implementation4');
|
||||||
|
const MigratableMockV1 = contract.fromArtifact('MigratableMockV1');
|
||||||
|
const MigratableMockV2 = contract.fromArtifact('MigratableMockV2');
|
||||||
|
const MigratableMockV3 = contract.fromArtifact('MigratableMockV3');
|
||||||
|
const InitializableMock = contract.fromArtifact('InitializableMock');
|
||||||
|
const DummyImplementation = contract.fromArtifact('DummyImplementation');
|
||||||
|
const ClashingImplementation = contract.fromArtifact('ClashingImplementation');
|
||||||
|
|
||||||
|
const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
|
||||||
|
const ADMIN_LABEL = 'eip1967.proxy.admin';
|
||||||
|
|
||||||
|
module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createProxy, accounts) {
|
||||||
|
const [proxyAdminAddress, proxyAdminOwner, anotherAccount] = accounts;
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.implementationV0 = (await DummyImplementation.new()).address;
|
||||||
|
this.implementationV1 = (await DummyImplementation.new()).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
const initializeData = Buffer.from('');
|
||||||
|
this.proxy = await createProxy(this.implementationV0, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyAdminOwner,
|
||||||
|
});
|
||||||
|
this.proxyAddress = this.proxy.address;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('implementation', function () {
|
||||||
|
it('returns the current implementation address', async function () {
|
||||||
|
const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
|
||||||
|
|
||||||
|
expect(implementation).to.be.equal(this.implementationV0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delegates to the implementation', async function () {
|
||||||
|
const dummy = new DummyImplementation(this.proxyAddress);
|
||||||
|
const value = await dummy.get();
|
||||||
|
|
||||||
|
expect(value).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('upgradeTo', function () {
|
||||||
|
describe('when the sender is the admin', function () {
|
||||||
|
const from = proxyAdminAddress;
|
||||||
|
|
||||||
|
describe('when the given implementation is different from the current one', function () {
|
||||||
|
it('upgrades to the requested implementation', async function () {
|
||||||
|
await this.proxy.upgradeTo(this.implementationV1, { from });
|
||||||
|
|
||||||
|
const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
|
||||||
|
expect(implementation).to.be.equal(this.implementationV1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits an event', async function () {
|
||||||
|
expectEvent(
|
||||||
|
await this.proxy.upgradeTo(this.implementationV1, { from }),
|
||||||
|
'Upgraded', {
|
||||||
|
implementation: this.implementationV1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given implementation is the zero address', function () {
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert(
|
||||||
|
this.proxy.upgradeTo(ZERO_ADDRESS, { from }),
|
||||||
|
'UpgradeableProxy: new implementation is not a contract',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the sender is not the admin', function () {
|
||||||
|
const from = anotherAccount;
|
||||||
|
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
this.proxy.upgradeTo(this.implementationV1, { from })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('upgradeToAndCall', function () {
|
||||||
|
describe('without migrations', function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.behavior = await InitializableMock.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the call does not fail', function () {
|
||||||
|
const initializeData = new InitializableMock('').contract.methods['initializeWithX(uint256)'](42).encodeABI();
|
||||||
|
|
||||||
|
describe('when the sender is the admin', function () {
|
||||||
|
const from = proxyAdminAddress;
|
||||||
|
const value = 1e5;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.receipt = await this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from, value });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('upgrades to the requested implementation', async function () {
|
||||||
|
const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
|
||||||
|
expect(implementation).to.be.equal(this.behavior.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits an event', function () {
|
||||||
|
expectEvent(this.receipt, 'Upgraded', { implementation: this.behavior.address });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the initializer function', async function () {
|
||||||
|
const migratable = new InitializableMock(this.proxyAddress);
|
||||||
|
const x = await migratable.x();
|
||||||
|
expect(x).to.be.bignumber.equal('42');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends given value to the proxy', async function () {
|
||||||
|
const balance = await web3.eth.getBalance(this.proxyAddress);
|
||||||
|
expect(balance.toString()).to.be.bignumber.equal(value.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('uses the storage of the proxy', async function () {
|
||||||
|
// storage layout should look as follows:
|
||||||
|
// - 0: Initializable storage
|
||||||
|
// - 1-50: Initailizable reserved storage (50 slots)
|
||||||
|
// - 51: initializerRan
|
||||||
|
// - 52: x
|
||||||
|
const storedValue = await Proxy.at(this.proxyAddress).getStorageAt(52);
|
||||||
|
expect(parseInt(storedValue)).to.eq(42);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the sender is not the admin', function () {
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: anotherAccount }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the call does fail', function () {
|
||||||
|
const initializeData = new InitializableMock('').contract.methods.fail().encodeABI();
|
||||||
|
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: proxyAdminAddress }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with migrations', function () {
|
||||||
|
describe('when the sender is the admin', function () {
|
||||||
|
const from = proxyAdminAddress;
|
||||||
|
const value = 1e5;
|
||||||
|
|
||||||
|
describe('when upgrading to V1', function () {
|
||||||
|
const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI();
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.behaviorV1 = await MigratableMockV1.new();
|
||||||
|
this.balancePreviousV1 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||||
|
this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV1.address, v1MigrationData, { from, value });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('upgrades to the requested version and emits an event', async function () {
|
||||||
|
const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
|
||||||
|
expect(implementation).to.be.equal(this.behaviorV1.address);
|
||||||
|
expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV1.address });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the \'initialize\' function and sends given value to the proxy', async function () {
|
||||||
|
const migratable = new MigratableMockV1(this.proxyAddress);
|
||||||
|
|
||||||
|
const x = await migratable.x();
|
||||||
|
expect(x).to.be.bignumber.equal('42');
|
||||||
|
|
||||||
|
const balance = await web3.eth.getBalance(this.proxyAddress);
|
||||||
|
expect(new BN(balance)).to.be.bignumber.equal(this.balancePreviousV1.addn(value));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when upgrading to V2', function () {
|
||||||
|
const v2MigrationData = new MigratableMockV2('').contract.methods.migrate(10, 42).encodeABI();
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.behaviorV2 = await MigratableMockV2.new();
|
||||||
|
this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||||
|
this.receipt =
|
||||||
|
await this.proxy.upgradeToAndCall(this.behaviorV2.address, v2MigrationData, { from, value });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('upgrades to the requested version and emits an event', async function () {
|
||||||
|
const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
|
||||||
|
expect(implementation).to.be.equal(this.behaviorV2.address);
|
||||||
|
expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV2.address });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the \'migrate\' function and sends given value to the proxy', async function () {
|
||||||
|
const migratable = new MigratableMockV2(this.proxyAddress);
|
||||||
|
|
||||||
|
const x = await migratable.x();
|
||||||
|
expect(x).to.be.bignumber.equal('10');
|
||||||
|
|
||||||
|
const y = await migratable.y();
|
||||||
|
expect(y).to.be.bignumber.equal('42');
|
||||||
|
|
||||||
|
const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||||
|
expect(balance).to.be.bignumber.equal(this.balancePreviousV2.addn(value));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when upgrading to V3', function () {
|
||||||
|
const v3MigrationData = new MigratableMockV3('').contract.methods['migrate()']().encodeABI();
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.behaviorV3 = await MigratableMockV3.new();
|
||||||
|
this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||||
|
this.receipt =
|
||||||
|
await this.proxy.upgradeToAndCall(this.behaviorV3.address, v3MigrationData, { from, value });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('upgrades to the requested version and emits an event', async function () {
|
||||||
|
const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
|
||||||
|
expect(implementation).to.be.equal(this.behaviorV3.address);
|
||||||
|
expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV3.address });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the \'migrate\' function and sends given value to the proxy', async function () {
|
||||||
|
const migratable = new MigratableMockV3(this.proxyAddress);
|
||||||
|
|
||||||
|
const x = await migratable.x();
|
||||||
|
expect(x).to.be.bignumber.equal('42');
|
||||||
|
|
||||||
|
const y = await migratable.y();
|
||||||
|
expect(y).to.be.bignumber.equal('10');
|
||||||
|
|
||||||
|
const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
|
||||||
|
expect(balance).to.be.bignumber.equal(this.balancePreviousV3.addn(value));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the sender is not the admin', function () {
|
||||||
|
const from = anotherAccount;
|
||||||
|
|
||||||
|
it('reverts', async function () {
|
||||||
|
const behaviorV1 = await MigratableMockV1.new();
|
||||||
|
const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI();
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
this.proxy.upgradeToAndCall(behaviorV1.address, v1MigrationData, { from }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('changeAdmin', function () {
|
||||||
|
describe('when the new proposed admin is not the zero address', function () {
|
||||||
|
const newAdmin = anotherAccount;
|
||||||
|
|
||||||
|
describe('when the sender is the admin', function () {
|
||||||
|
beforeEach('transferring', async function () {
|
||||||
|
this.receipt = await this.proxy.changeAdmin(newAdmin, { from: proxyAdminAddress });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns new proxy admin', async function () {
|
||||||
|
const newProxyAdmin = await this.proxy.admin.call({ from: newAdmin });
|
||||||
|
expect(newProxyAdmin).to.be.equal(anotherAccount);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits an event', function () {
|
||||||
|
expectEvent(this.receipt, 'AdminChanged', {
|
||||||
|
previousAdmin: proxyAdminAddress,
|
||||||
|
newAdmin: newAdmin,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the sender is not the admin', function () {
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert.unspecified(this.proxy.changeAdmin(newAdmin, { from: anotherAccount }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the new proposed admin is the zero address', function () {
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert(
|
||||||
|
this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }),
|
||||||
|
'TransparentUpgradeableProxy: new admin is the zero address',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('storage', function () {
|
||||||
|
it('should store the implementation address in specified location', async function () {
|
||||||
|
const slot = '0x' + new BN(keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16);
|
||||||
|
const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot));
|
||||||
|
expect(implementation).to.be.equal(this.implementationV0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should store the admin proxy in specified location', async function () {
|
||||||
|
const slot = '0x' + new BN(keccak256(Buffer.from(ADMIN_LABEL))).subn(1).toString(16);
|
||||||
|
const proxyAdmin = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot));
|
||||||
|
expect(proxyAdmin).to.be.equal(proxyAdminAddress);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('transparent proxy', function () {
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
const initializeData = Buffer.from('');
|
||||||
|
this.impl = await ClashingImplementation.new();
|
||||||
|
this.proxy = await createProxy(this.impl.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||||
|
|
||||||
|
this.clashing = new ClashingImplementation(this.proxy.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('proxy admin cannot call delegated functions', async function () {
|
||||||
|
await expectRevert(
|
||||||
|
this.clashing.delegatedFunction({ from: proxyAdminAddress }),
|
||||||
|
'TransparentUpgradeableProxy: admin cannot fallback to proxy target',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when function names clash', function () {
|
||||||
|
it('when sender is proxy admin should run the proxy function', async function () {
|
||||||
|
const value = await this.proxy.admin.call({ from: proxyAdminAddress });
|
||||||
|
expect(value).to.be.equal(proxyAdminAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when sender is other should delegate to implementation', async function () {
|
||||||
|
const value = await this.proxy.admin.call({ from: anotherAccount });
|
||||||
|
expect(value).to.be.equal('0x0000000000000000000000000000000011111142');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('regression', () => {
|
||||||
|
const initializeData = Buffer.from('');
|
||||||
|
|
||||||
|
it('should add new function', async () => {
|
||||||
|
const instance1 = await Implementation1.new();
|
||||||
|
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||||
|
|
||||||
|
const proxyInstance1 = new Implementation1(proxy.address);
|
||||||
|
await proxyInstance1.setValue(42);
|
||||||
|
|
||||||
|
const instance2 = await Implementation2.new();
|
||||||
|
await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
|
||||||
|
|
||||||
|
const proxyInstance2 = new Implementation2(proxy.address);
|
||||||
|
const res = await proxyInstance2.getValue();
|
||||||
|
expect(res.toString()).to.eq('42');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove function', async () => {
|
||||||
|
const instance2 = await Implementation2.new();
|
||||||
|
const proxy = await createProxy(instance2.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||||
|
|
||||||
|
const proxyInstance2 = new Implementation2(proxy.address);
|
||||||
|
await proxyInstance2.setValue(42);
|
||||||
|
const res = await proxyInstance2.getValue();
|
||||||
|
expect(res.toString()).to.eq('42');
|
||||||
|
|
||||||
|
const instance1 = await Implementation1.new();
|
||||||
|
await proxy.upgradeTo(instance1.address, { from: proxyAdminAddress });
|
||||||
|
|
||||||
|
const proxyInstance1 = new Implementation2(proxy.address);
|
||||||
|
await expectRevert.unspecified(proxyInstance1.getValue());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change function signature', async () => {
|
||||||
|
const instance1 = await Implementation1.new();
|
||||||
|
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||||
|
|
||||||
|
const proxyInstance1 = new Implementation1(proxy.address);
|
||||||
|
await proxyInstance1.setValue(42);
|
||||||
|
|
||||||
|
const instance3 = await Implementation3.new();
|
||||||
|
await proxy.upgradeTo(instance3.address, { from: proxyAdminAddress });
|
||||||
|
const proxyInstance3 = new Implementation3(proxy.address);
|
||||||
|
|
||||||
|
const res = await proxyInstance3.getValue(8);
|
||||||
|
expect(res.toString()).to.eq('50');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add fallback function', async () => {
|
||||||
|
const initializeData = Buffer.from('');
|
||||||
|
const instance1 = await Implementation1.new();
|
||||||
|
const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||||
|
|
||||||
|
const instance4 = await Implementation4.new();
|
||||||
|
await proxy.upgradeTo(instance4.address, { from: proxyAdminAddress });
|
||||||
|
const proxyInstance4 = new Implementation4(proxy.address);
|
||||||
|
|
||||||
|
const data = '';
|
||||||
|
await web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data });
|
||||||
|
|
||||||
|
const res = await proxyInstance4.getValue();
|
||||||
|
expect(res.toString()).to.eq('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove fallback function', async () => {
|
||||||
|
const instance4 = await Implementation4.new();
|
||||||
|
const proxy = await createProxy(instance4.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
|
||||||
|
|
||||||
|
const instance2 = await Implementation2.new();
|
||||||
|
await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
|
||||||
|
|
||||||
|
const data = '';
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const proxyInstance2 = new Implementation2(proxy.address);
|
||||||
|
const res = await proxyInstance2.getValue();
|
||||||
|
expect(res.toString()).to.eq('0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
17
test/proxy/TransparentUpgradeableProxy.test.js
Normal file
17
test/proxy/TransparentUpgradeableProxy.test.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const { accounts, contract } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const shouldBehaveLikeUpgradeableProxy = require('./UpgradeableProxy.behaviour');
|
||||||
|
const shouldBehaveLikeTransparentUpgradeableProxy = require('./TransparentUpgradeableProxy.behaviour');
|
||||||
|
|
||||||
|
const TransparentUpgradeableProxy = contract.fromArtifact('TransparentUpgradeableProxy');
|
||||||
|
|
||||||
|
describe('TransparentUpgradeableProxy', function () {
|
||||||
|
const [proxyAdminAddress, proxyAdminOwner] = accounts;
|
||||||
|
|
||||||
|
const createProxy = async function (logic, admin, initData, opts) {
|
||||||
|
return TransparentUpgradeableProxy.new(logic, admin, initData, opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldBehaveLikeUpgradeableProxy(createProxy, proxyAdminAddress, proxyAdminOwner);
|
||||||
|
shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts);
|
||||||
|
});
|
||||||
216
test/proxy/UpgradeableProxy.behaviour.js
Normal file
216
test/proxy/UpgradeableProxy.behaviour.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
const { contract, web3 } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
|
||||||
|
const { toChecksumAddress, keccak256 } = require('ethereumjs-util');
|
||||||
|
|
||||||
|
const { expect } = require('chai');
|
||||||
|
|
||||||
|
const DummyImplementation = contract.fromArtifact('DummyImplementation');
|
||||||
|
|
||||||
|
const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
|
||||||
|
|
||||||
|
module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAdminAddress, proxyCreator) {
|
||||||
|
it('cannot be initialized with a non-contract address', async function () {
|
||||||
|
const nonContractAddress = proxyCreator;
|
||||||
|
const initializeData = Buffer.from('');
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
createProxy(nonContractAddress, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
before('deploy implementation', async function () {
|
||||||
|
this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address);
|
||||||
|
});
|
||||||
|
|
||||||
|
const assertProxyInitialization = function ({ value, balance }) {
|
||||||
|
it('sets the implementation address', async function () {
|
||||||
|
const slot = '0x' + new BN(keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16);
|
||||||
|
const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxy, slot));
|
||||||
|
expect(implementation).to.be.equal(this.implementation);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes the proxy', async function () {
|
||||||
|
const dummy = new DummyImplementation(this.proxy);
|
||||||
|
expect(await dummy.value()).to.be.bignumber.equal(value.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has expected balance', async function () {
|
||||||
|
expect(await web3.eth.getBalance(this.proxy)).to.be.bignumber.equal(balance.toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('without initialization', function () {
|
||||||
|
const initializeData = Buffer.from('');
|
||||||
|
|
||||||
|
describe('when not sending balance', function () {
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({ value: 0, balance: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when sending some balance', function () {
|
||||||
|
const value = 10e5;
|
||||||
|
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({ value: 0, balance: value });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initialization without parameters', function () {
|
||||||
|
describe('non payable', function () {
|
||||||
|
const expectedInitializedValue = 10;
|
||||||
|
const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI();
|
||||||
|
|
||||||
|
describe('when not sending balance', function () {
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({
|
||||||
|
value: expectedInitializedValue,
|
||||||
|
balance: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when sending some balance', function () {
|
||||||
|
const value = 10e5;
|
||||||
|
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('payable', function () {
|
||||||
|
const expectedInitializedValue = 100;
|
||||||
|
const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI();
|
||||||
|
|
||||||
|
describe('when not sending balance', function () {
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({
|
||||||
|
value: expectedInitializedValue,
|
||||||
|
balance: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when sending some balance', function () {
|
||||||
|
const value = 10e5;
|
||||||
|
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({
|
||||||
|
value: expectedInitializedValue,
|
||||||
|
balance: value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initialization with parameters', function () {
|
||||||
|
describe('non payable', function () {
|
||||||
|
const expectedInitializedValue = 10;
|
||||||
|
const initializeData = new DummyImplementation('').contract
|
||||||
|
.methods['initializeNonPayable(uint256)'](expectedInitializedValue).encodeABI();
|
||||||
|
|
||||||
|
describe('when not sending balance', function () {
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({
|
||||||
|
value: expectedInitializedValue,
|
||||||
|
balance: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when sending some balance', function () {
|
||||||
|
const value = 10e5;
|
||||||
|
|
||||||
|
it('reverts', async function () {
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('payable', function () {
|
||||||
|
const expectedInitializedValue = 42;
|
||||||
|
const initializeData = new DummyImplementation('').contract
|
||||||
|
.methods['initializePayable(uint256)'](expectedInitializedValue).encodeABI();
|
||||||
|
|
||||||
|
describe('when not sending balance', function () {
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({
|
||||||
|
value: expectedInitializedValue,
|
||||||
|
balance: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when sending some balance', function () {
|
||||||
|
const value = 10e5;
|
||||||
|
|
||||||
|
beforeEach('creating proxy', async function () {
|
||||||
|
this.proxy = (
|
||||||
|
await createProxy(this.implementation, proxyAdminAddress, initializeData, {
|
||||||
|
from: proxyCreator,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
).address;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertProxyInitialization({
|
||||||
|
value: expectedInitializedValue,
|
||||||
|
balance: value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
15
test/proxy/UpgradeableProxy.test.js
Normal file
15
test/proxy/UpgradeableProxy.test.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const { accounts, contract } = require('@openzeppelin/test-environment');
|
||||||
|
|
||||||
|
const shouldBehaveLikeUpgradeableProxy = require('./UpgradeableProxy.behaviour');
|
||||||
|
|
||||||
|
const UpgradeableProxy = contract.fromArtifact('UpgradeableProxy');
|
||||||
|
|
||||||
|
describe('UpgradeableProxy', function () {
|
||||||
|
const [proxyAdminOwner] = accounts;
|
||||||
|
|
||||||
|
const createProxy = async function (implementation, _admin, initData, opts) {
|
||||||
|
return UpgradeableProxy.new(implementation, initData, opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldBehaveLikeUpgradeableProxy(createProxy, undefined, proxyAdminOwner);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user