From ae64ea77dcf67fa481bf99eb56f2f9dcab8ad787 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 23 Feb 2021 20:03:11 +0100 Subject: [PATCH 1/7] add uups implementation --- contracts/proxy/uups/UUPS.sol | 23 +++++++++++++++++++++++ contracts/proxy/uups/UUPSProxiable.sol | 16 ++++++++++++++++ contracts/proxy/uups/UUPSProxy.sol | 20 ++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 contracts/proxy/uups/UUPS.sol create mode 100644 contracts/proxy/uups/UUPSProxiable.sol create mode 100644 contracts/proxy/uups/UUPSProxy.sol diff --git a/contracts/proxy/uups/UUPS.sol b/contracts/proxy/uups/UUPS.sol new file mode 100644 index 00000000000..5b0f690b4c6 --- /dev/null +++ b/contracts/proxy/uups/UUPS.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +library UUPS { + // bytes32 private constant _UUPS_SLOT = keccak256("PROXIABLE"); + bytes32 private constant _UUPS_SLOT = 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; + + struct UUPSStore { + address implementation; + } + + function instance() internal pure returns (UUPSStore storage uups) { + bytes32 position = _UUPS_SLOT; + assembly { + uups.slot := position + } + } + + function uuid() internal pure returns (bytes32) { + return _UUPS_SLOT; + } +} diff --git a/contracts/proxy/uups/UUPSProxiable.sol b/contracts/proxy/uups/UUPSProxiable.sol new file mode 100644 index 00000000000..a3f32a3c7a5 --- /dev/null +++ b/contracts/proxy/uups/UUPSProxiable.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./UUPS.sol"; + +contract UUPSProxiable { + function proxiableUUID() public pure returns (bytes32) { + return UUPS.uuid(); + } + + function _updateCodeAddress(address newAddress) internal { + require(UUPS.uuid() == UUPSProxiable(newAddress).proxiableUUID(), "Not compatible"); + UUPS.instance().implementation = newAddress; + } +} diff --git a/contracts/proxy/uups/UUPSProxy.sol b/contracts/proxy/uups/UUPSProxy.sol new file mode 100644 index 00000000000..9a3c465f487 --- /dev/null +++ b/contracts/proxy/uups/UUPSProxy.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./UUPS.sol"; +import "../Proxy.sol"; +import "../../utils/Address.sol"; + +contract UUPSProxy is Proxy { + constructor(address _logic, bytes memory _data) payable { + UUPS.instance().implementation = _logic; + if (_data.length > 0) { + Address.functionCall(_logic, _data); + } + } + + function _implementation() internal view virtual override returns (address) { + return UUPS.instance().implementation; + } +} From 7812d1d9d5f9e119a10759de83fe91cf124b20bb Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 24 Feb 2021 09:47:56 +0100 Subject: [PATCH 2/7] fix UUPSProxy and add tests --- .../mocks/DummyImplementationProxiable.sol | 18 ++++++ contracts/proxy/uups/UUPSProxiable.sol | 2 +- contracts/proxy/uups/UUPSProxy.sol | 4 +- ...eProxy.behaviour.js => Proxy.behaviour.js} | 23 +++++--- test/proxy/UpgradeableProxy.test.js | 4 +- .../{ => transparent}/ProxyAdmin.test.js | 0 .../TransparentUpgradeableProxy.test.js | 4 +- test/proxy/uups/UUPSProxy.test.js | 56 +++++++++++++++++++ 8 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 contracts/mocks/DummyImplementationProxiable.sol rename test/proxy/{UpgradeableProxy.behaviour.js => Proxy.behaviour.js} (89%) rename test/proxy/{ => transparent}/ProxyAdmin.test.js (100%) create mode 100644 test/proxy/uups/UUPSProxy.test.js diff --git a/contracts/mocks/DummyImplementationProxiable.sol b/contracts/mocks/DummyImplementationProxiable.sol new file mode 100644 index 00000000000..eeee85b412f --- /dev/null +++ b/contracts/mocks/DummyImplementationProxiable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./DummyImplementation.sol"; +import "../proxy/uups/UUPSProxiable.sol"; + +contract DummyImplementationProxiable is DummyImplementation, UUPSProxiable { + function updateCodeAddress(address newImplementation) public { + _updateCodeAddress(newImplementation); + } +} + +contract DummyImplementationV2Proxiable is DummyImplementationV2, UUPSProxiable { + function updateCodeAddress(address newImplementation) public { + _updateCodeAddress(newImplementation); + } +} diff --git a/contracts/proxy/uups/UUPSProxiable.sol b/contracts/proxy/uups/UUPSProxiable.sol index a3f32a3c7a5..d6a79da8462 100644 --- a/contracts/proxy/uups/UUPSProxiable.sol +++ b/contracts/proxy/uups/UUPSProxiable.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import "./UUPS.sol"; -contract UUPSProxiable { +abstract contract UUPSProxiable { function proxiableUUID() public pure returns (bytes32) { return UUPS.uuid(); } diff --git a/contracts/proxy/uups/UUPSProxy.sol b/contracts/proxy/uups/UUPSProxy.sol index 9a3c465f487..67228c09ac5 100644 --- a/contracts/proxy/uups/UUPSProxy.sol +++ b/contracts/proxy/uups/UUPSProxy.sol @@ -3,14 +3,16 @@ pragma solidity ^0.8.0; import "./UUPS.sol"; +import "./UUPSProxiable.sol"; import "../Proxy.sol"; import "../../utils/Address.sol"; contract UUPSProxy is Proxy { constructor(address _logic, bytes memory _data) payable { + require(UUPS.uuid() == UUPSProxiable(_logic).proxiableUUID(), "Not compatible"); UUPS.instance().implementation = _logic; if (_data.length > 0) { - Address.functionCall(_logic, _data); + Address.functionDelegateCall(_logic, _data); } } diff --git a/test/proxy/UpgradeableProxy.behaviour.js b/test/proxy/Proxy.behaviour.js similarity index 89% rename from test/proxy/UpgradeableProxy.behaviour.js rename to test/proxy/Proxy.behaviour.js index dc6ffb1fbe5..e31590d2726 100644 --- a/test/proxy/UpgradeableProxy.behaviour.js +++ b/test/proxy/Proxy.behaviour.js @@ -11,7 +11,13 @@ function toChecksumAddress (address) { return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0')); } -module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAdminAddress, proxyCreator) { +module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, proxyCreator, opts = {}) { + const { artefact, slot } = Object.assign({ + artefact: DummyImplementation, + slot: '0x' + new BN(ethereumjsUtil.keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16), + }, opts); + + it('cannot be initialized with a non-contract address', async function () { const nonContractAddress = proxyCreator; const initializeData = Buffer.from(''); @@ -23,18 +29,17 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd }); before('deploy implementation', async function () { - this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address); + this.implementation = web3.utils.toChecksumAddress((await artefact.new()).address); }); const assertProxyInitialization = function ({ value, balance }) { it('sets the implementation address', async function () { - const slot = '0x' + new BN(ethereumjsUtil.keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16); const implementation = toChecksumAddress((await web3.eth.getStorageAt(this.proxy, slot)).substr(-40)); expect(implementation).to.be.equal(this.implementation); }); it('initializes the proxy', async function () { - const dummy = new DummyImplementation(this.proxy); + const dummy = new artefact(this.proxy); expect(await dummy.value()).to.be.bignumber.equal(value.toString()); }); @@ -77,7 +82,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd describe('initialization without parameters', function () { describe('non payable', function () { const expectedInitializedValue = 10; - const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI(); + const initializeData = new artefact('').contract.methods['initializeNonPayable()']().encodeABI(); describe('when not sending balance', function () { beforeEach('creating proxy', async function () { @@ -107,7 +112,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd describe('payable', function () { const expectedInitializedValue = 100; - const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI(); + const initializeData = new artefact('').contract.methods['initializePayable()']().encodeABI(); describe('when not sending balance', function () { beforeEach('creating proxy', async function () { @@ -147,7 +152,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd describe('initialization with parameters', function () { describe('non payable', function () { const expectedInitializedValue = 10; - const initializeData = new DummyImplementation('').contract + const initializeData = new artefact('').contract .methods.initializeNonPayableWithValue(expectedInitializedValue).encodeABI(); describe('when not sending balance', function () { @@ -178,7 +183,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd describe('payable', function () { const expectedInitializedValue = 42; - const initializeData = new DummyImplementation('').contract + const initializeData = new artefact('').contract .methods.initializePayableWithValue(expectedInitializedValue).encodeABI(); describe('when not sending balance', function () { @@ -216,7 +221,7 @@ module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAd }); describe('reverting initialization', function () { - const initializeData = new DummyImplementation('').contract + const initializeData = new artefact('').contract .methods.reverts().encodeABI(); it('reverts', async function () { diff --git a/test/proxy/UpgradeableProxy.test.js b/test/proxy/UpgradeableProxy.test.js index 12d54bdbd42..274e632cb28 100644 --- a/test/proxy/UpgradeableProxy.test.js +++ b/test/proxy/UpgradeableProxy.test.js @@ -1,4 +1,4 @@ -const shouldBehaveLikeUpgradeableProxy = require('./UpgradeableProxy.behaviour'); +const shouldBehaveLikeProxy = require('./Proxy.behaviour'); const UpgradeableProxy = artifacts.require('UpgradeableProxy'); @@ -9,5 +9,5 @@ contract('UpgradeableProxy', function (accounts) { return UpgradeableProxy.new(implementation, initData, opts); }; - shouldBehaveLikeUpgradeableProxy(createProxy, undefined, proxyAdminOwner); + shouldBehaveLikeProxy(createProxy, undefined, proxyAdminOwner); }); diff --git a/test/proxy/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js similarity index 100% rename from test/proxy/ProxyAdmin.test.js rename to test/proxy/transparent/ProxyAdmin.test.js diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.test.js b/test/proxy/transparent/TransparentUpgradeableProxy.test.js index cf8206996bc..86dd55d32c1 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.test.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.test.js @@ -1,4 +1,4 @@ -const shouldBehaveLikeUpgradeableProxy = require('../UpgradeableProxy.behaviour'); +const shouldBehaveLikeProxy = require('../Proxy.behaviour'); const shouldBehaveLikeTransparentUpgradeableProxy = require('./TransparentUpgradeableProxy.behaviour'); const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy'); @@ -10,6 +10,6 @@ contract('TransparentUpgradeableProxy', function (accounts) { return TransparentUpgradeableProxy.new(logic, admin, initData, opts); }; - shouldBehaveLikeUpgradeableProxy(createProxy, proxyAdminAddress, proxyAdminOwner); + shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyAdminOwner); shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts); }); diff --git a/test/proxy/uups/UUPSProxy.test.js b/test/proxy/uups/UUPSProxy.test.js new file mode 100644 index 00000000000..ae142ca5a57 --- /dev/null +++ b/test/proxy/uups/UUPSProxy.test.js @@ -0,0 +1,56 @@ +const { expectRevert } = require('@openzeppelin/test-helpers'); +const ethereumjsUtil = require('ethereumjs-util'); + +const shouldBehaveLikeProxy = require('../Proxy.behaviour'); + +const DummyImplementation = artifacts.require('DummyImplementation') +const DummyImplementationProxiable = artifacts.require('DummyImplementationProxiable') +const DummyImplementationV2Proxiable = artifacts.require('DummyImplementationV2Proxiable') +const UUPSProxy = artifacts.require('UUPSProxy'); + +const UUPS_UUID = '0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7'; + +function toChecksumAddress (address) { + return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0')); +} + +contract('UUPSProxy', function (accounts) { + const [ proxyCreator ] = accounts; + + const createProxy = async function (implementation, _admin, initData, opts) { + return UUPSProxy.new(implementation, initData, opts); + }; + + shouldBehaveLikeProxy(createProxy, undefined, proxyCreator, { + artefact: DummyImplementationProxiable, + slot: UUPS_UUID, + }); + + context('UUPS', function () { + beforeEach(async function () { + this.implementation = await DummyImplementation.new(); + this.implementationproxiable = await DummyImplementationProxiable.new(); + this.implementationproxiablev2 = await DummyImplementationV2Proxiable.new(); + const { address } = await createProxy(this.implementationproxiable.address, undefined, '0x', { from: proxyCreator }); + this.dummy = new DummyImplementationProxiable(address); + }); + + it('check uuid', async function() { + expect(await this.dummy.proxiableUUID()).to.be.equal(UUPS_UUID); + }); + + it('can update to proxiable', async function() { + await this.dummy.updateCodeAddress(this.implementationproxiablev2.address); + const implementation = toChecksumAddress((await web3.eth.getStorageAt(this.dummy.address, UUPS_UUID)).substr(-40)); + expect(implementation).to.be.equal(this.implementationproxiablev2.address); + }); + + it('does not deploy with non-proxiable', async function() { + await expectRevert.unspecified(createProxy(this.implementation.address, undefined, '0x', { from: proxyCreator })); + }); + + it('does not update to non-proxiable', async function() { + await expectRevert.unspecified(this.dummy.updateCodeAddress(this.implementation.address)); + }); + }); +}); From 73b18f7529ded530f1baab9339516b29a814109c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 26 Feb 2021 13:34:41 +0100 Subject: [PATCH 3/7] refactor uups: proxy upgradeable is now uupsproxy --- contracts/proxy/UpgradeableProxy.sol | 78 ------------------- .../TransparentUpgradeableProxy.sol | 6 +- contracts/proxy/uups/UUPS.sol | 51 +++++++++--- contracts/proxy/uups/UUPSProxiable.sol | 10 +-- contracts/proxy/uups/UUPSProxy.sol | 36 +++++++-- test/proxy/Proxy.behaviour.js | 23 +++--- test/proxy/UpgradeableProxy.test.js | 13 ---- .../TransparentUpgradeableProxy.behaviour.js | 2 +- test/proxy/uups/UUPSProxiable.test.js | 53 +++++++++++++ test/proxy/uups/UUPSProxy.test.js | 47 +---------- 10 files changed, 140 insertions(+), 179 deletions(-) delete mode 100644 contracts/proxy/UpgradeableProxy.sol delete mode 100644 test/proxy/UpgradeableProxy.test.js create mode 100644 test/proxy/uups/UUPSProxiable.test.js diff --git a/contracts/proxy/UpgradeableProxy.sol b/contracts/proxy/UpgradeableProxy.sol deleted file mode 100644 index 8dd86464026..00000000000 --- a/contracts/proxy/UpgradeableProxy.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./Proxy.sol"; -import "../utils/Address.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 UpgradeableProxy is Proxy { - /** - * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. - * - * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded - * function call, and allows initializating the storage of the proxy like a Solidity constructor. - */ - 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); - } - } - - /** - * @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 { - _setImplementation(newImplementation); - emit Upgraded(newImplementation); - } - - /** - * @dev Stores a new address in the EIP1967 implementation slot. - */ - function _setImplementation(address newImplementation) private { - 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) - } - } -} diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 4ce4eec36d5..120762d0dbd 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import "../UpgradeableProxy.sol"; +import "../uups/UUPSProxy.sol"; /** * @dev This contract implements a proxy that is upgradeable by an admin. @@ -25,12 +25,12 @@ import "../UpgradeableProxy.sol"; * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. */ -contract TransparentUpgradeableProxy is UpgradeableProxy { +contract TransparentUpgradeableProxy is UUPSProxy { /** * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {UpgradeableProxy-constructor}. */ - constructor(address _logic, address admin_, bytes memory _data) payable UpgradeableProxy(_logic, _data) { + constructor(address _logic, address admin_, bytes memory _data) payable UUPSProxy(_logic, _data) { assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); _setAdmin(admin_); } diff --git a/contracts/proxy/uups/UUPS.sol b/contracts/proxy/uups/UUPS.sol index 5b0f690b4c6..caad0ec006c 100644 --- a/contracts/proxy/uups/UUPS.sol +++ b/contracts/proxy/uups/UUPS.sol @@ -2,22 +2,49 @@ pragma solidity ^0.8.0; -library UUPS { - // bytes32 private constant _UUPS_SLOT = keccak256("PROXIABLE"); - bytes32 private constant _UUPS_SLOT = 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; +import "../../utils/Address.sol"; - struct UUPSStore { - address implementation; +abstract contract UUPS { + /** + * @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 Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Return the UUPS uuid. + */ + function _uuid() internal view virtual returns (bytes32) { + return _IMPLEMENTATION_SLOT; } - function instance() internal pure returns (UUPSStore storage uups) { - bytes32 position = _UUPS_SLOT; - assembly { - uups.slot := position - } + /** + * @dev Upgrades the proxy to a new implementation. + * + * Emits an {Upgraded} event. + */ + function _upgradeTo(address newImplementation) internal virtual { + _setImplementation(newImplementation); + emit Upgraded(newImplementation); } - function uuid() internal pure returns (bytes32) { - return _UUPS_SLOT; + /** + * @dev Stores a new address in the EIP1967 implementation slot. + */ + function _setImplementation(address newImplementation) private { + require(Address.isContract(newImplementation), "UUPS: new implementation is not a contract"); + + bytes32 slot = _IMPLEMENTATION_SLOT; + + // solhint-disable-next-line no-inline-assembly + assembly { + sstore(slot, newImplementation) + } } } diff --git a/contracts/proxy/uups/UUPSProxiable.sol b/contracts/proxy/uups/UUPSProxiable.sol index d6a79da8462..c2af91af540 100644 --- a/contracts/proxy/uups/UUPSProxiable.sol +++ b/contracts/proxy/uups/UUPSProxiable.sol @@ -4,13 +4,13 @@ pragma solidity ^0.8.0; import "./UUPS.sol"; -abstract contract UUPSProxiable { - function proxiableUUID() public pure returns (bytes32) { - return UUPS.uuid(); +abstract contract UUPSProxiable is UUPS { + function proxiableUUID() public view returns (bytes32) { + return _uuid(); } function _updateCodeAddress(address newAddress) internal { - require(UUPS.uuid() == UUPSProxiable(newAddress).proxiableUUID(), "Not compatible"); - UUPS.instance().implementation = newAddress; + require(_uuid() == UUPSProxiable(newAddress).proxiableUUID(), "UUPS: new impelmentation is not compatible"); + _upgradeTo(newAddress); } } diff --git a/contracts/proxy/uups/UUPSProxy.sol b/contracts/proxy/uups/UUPSProxy.sol index 67228c09ac5..9529f35920f 100644 --- a/contracts/proxy/uups/UUPSProxy.sol +++ b/contracts/proxy/uups/UUPSProxy.sol @@ -3,20 +3,40 @@ pragma solidity ^0.8.0; import "./UUPS.sol"; -import "./UUPSProxiable.sol"; import "../Proxy.sol"; -import "../../utils/Address.sol"; -contract UUPSProxy is Proxy { +/** + * @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 UUPSProxy is Proxy, UUPS { + /** + * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. + * + * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded + * function call, and allows initializating the storage of the proxy like a Solidity constructor. + */ constructor(address _logic, bytes memory _data) payable { - require(UUPS.uuid() == UUPSProxiable(_logic).proxiableUUID(), "Not compatible"); - UUPS.instance().implementation = _logic; - if (_data.length > 0) { + assert(_uuid() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); + _upgradeTo(_logic); + if(_data.length > 0) { Address.functionDelegateCall(_logic, _data); } } - function _implementation() internal view virtual override returns (address) { - return UUPS.instance().implementation; + /** + * @dev Returns the current implementation address. + */ + function _implementation() internal view virtual override returns (address impl) { + bytes32 slot = _uuid(); + // solhint-disable-next-line no-inline-assembly + assembly { + impl := sload(slot) + } } } diff --git a/test/proxy/Proxy.behaviour.js b/test/proxy/Proxy.behaviour.js index e31590d2726..8cb457b06c6 100644 --- a/test/proxy/Proxy.behaviour.js +++ b/test/proxy/Proxy.behaviour.js @@ -11,13 +11,7 @@ function toChecksumAddress (address) { return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0')); } -module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, proxyCreator, opts = {}) { - const { artefact, slot } = Object.assign({ - artefact: DummyImplementation, - slot: '0x' + new BN(ethereumjsUtil.keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16), - }, opts); - - +module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, proxyCreator) { it('cannot be initialized with a non-contract address', async function () { const nonContractAddress = proxyCreator; const initializeData = Buffer.from(''); @@ -29,17 +23,18 @@ module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, }); before('deploy implementation', async function () { - this.implementation = web3.utils.toChecksumAddress((await artefact.new()).address); + 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(ethereumjsUtil.keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16); const implementation = toChecksumAddress((await web3.eth.getStorageAt(this.proxy, slot)).substr(-40)); expect(implementation).to.be.equal(this.implementation); }); it('initializes the proxy', async function () { - const dummy = new artefact(this.proxy); + const dummy = new DummyImplementation(this.proxy); expect(await dummy.value()).to.be.bignumber.equal(value.toString()); }); @@ -82,7 +77,7 @@ module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, describe('initialization without parameters', function () { describe('non payable', function () { const expectedInitializedValue = 10; - const initializeData = new artefact('').contract.methods['initializeNonPayable()']().encodeABI(); + const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI(); describe('when not sending balance', function () { beforeEach('creating proxy', async function () { @@ -112,7 +107,7 @@ module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, describe('payable', function () { const expectedInitializedValue = 100; - const initializeData = new artefact('').contract.methods['initializePayable()']().encodeABI(); + const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI(); describe('when not sending balance', function () { beforeEach('creating proxy', async function () { @@ -152,7 +147,7 @@ module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, describe('initialization with parameters', function () { describe('non payable', function () { const expectedInitializedValue = 10; - const initializeData = new artefact('').contract + const initializeData = new DummyImplementation('').contract .methods.initializeNonPayableWithValue(expectedInitializedValue).encodeABI(); describe('when not sending balance', function () { @@ -183,7 +178,7 @@ module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, describe('payable', function () { const expectedInitializedValue = 42; - const initializeData = new artefact('').contract + const initializeData = new DummyImplementation('').contract .methods.initializePayableWithValue(expectedInitializedValue).encodeABI(); describe('when not sending balance', function () { @@ -221,7 +216,7 @@ module.exports = function shouldBehaveLikeProxy (createProxy, proxyAdminAddress, }); describe('reverting initialization', function () { - const initializeData = new artefact('').contract + const initializeData = new DummyImplementation('').contract .methods.reverts().encodeABI(); it('reverts', async function () { diff --git a/test/proxy/UpgradeableProxy.test.js b/test/proxy/UpgradeableProxy.test.js deleted file mode 100644 index 274e632cb28..00000000000 --- a/test/proxy/UpgradeableProxy.test.js +++ /dev/null @@ -1,13 +0,0 @@ -const shouldBehaveLikeProxy = require('./Proxy.behaviour'); - -const UpgradeableProxy = artifacts.require('UpgradeableProxy'); - -contract('UpgradeableProxy', function (accounts) { - const [proxyAdminOwner] = accounts; - - const createProxy = async function (implementation, _admin, initData, opts) { - return UpgradeableProxy.new(implementation, initData, opts); - }; - - shouldBehaveLikeProxy(createProxy, undefined, proxyAdminOwner); -}); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 287c407d6b6..2b66c00ee17 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -80,7 +80,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro it('reverts', async function () { await expectRevert( this.proxy.upgradeTo(ZERO_ADDRESS, { from }), - 'UpgradeableProxy: new implementation is not a contract', + 'UUPS: new implementation is not a contract', ); }); }); diff --git a/test/proxy/uups/UUPSProxiable.test.js b/test/proxy/uups/UUPSProxiable.test.js new file mode 100644 index 00000000000..e62c24e07b2 --- /dev/null +++ b/test/proxy/uups/UUPSProxiable.test.js @@ -0,0 +1,53 @@ +const { expectRevert } = require('@openzeppelin/test-helpers'); +const ethereumjsUtil = require('ethereumjs-util'); + +const DummyImplementation = artifacts.require('DummyImplementation'); +const DummyImplementationProxiable = artifacts.require('DummyImplementationProxiable'); +const DummyImplementationV2Proxiable = artifacts.require('DummyImplementationV2Proxiable'); +const UUPSProxy = artifacts.require('UUPSProxy'); + +const UUPS_UUID = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'; + +function toChecksumAddress (address) { + return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0')); +} + +contract('UUPSProxiable', function (accounts) { + const [ proxyCreator ] = accounts; + + const createProxy = async function (implementation, _admin, initData, opts) { + return UUPSProxy.new(implementation, initData, opts); + }; + + context('UUPS', function () { + beforeEach(async function () { + this.impl = await DummyImplementation.new(); + this.implproxiable = await DummyImplementationProxiable.new(); + this.implproxiablev2 = await DummyImplementationV2Proxiable.new(); + const { address } = await createProxy(this.implproxiable.address, undefined, '0x', { from: proxyCreator }); + this.dummy = new DummyImplementationProxiable(address); + }); + + it('check uuid', async function () { + expect(await this.dummy.proxiableUUID()).to.be.equal(UUPS_UUID); + }); + + it('can update to proxiable', async function () { + await this.dummy.updateCodeAddress(this.implproxiablev2.address); + const implementation = toChecksumAddress((await web3.eth.getStorageAt( + this.dummy.address, + UUPS_UUID, + )).substr(-40)); + expect(implementation).to.be.equal(this.implproxiablev2.address); + }); + + it('does not deploy with non-proxiable', async function () { + this.skip(); // NOT SUPPORTED YET + await expectRevert.unspecified(createProxy(this.impl.address, undefined, '0x', { from: proxyCreator })); + }); + + it('does not update to non-proxiable', async function () { + await expectRevert.unspecified(this.dummy.updateCodeAddress(this.impl.address)); + }); + }); +}); diff --git a/test/proxy/uups/UUPSProxy.test.js b/test/proxy/uups/UUPSProxy.test.js index ae142ca5a57..a960c0d8a2b 100644 --- a/test/proxy/uups/UUPSProxy.test.js +++ b/test/proxy/uups/UUPSProxy.test.js @@ -1,56 +1,13 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); -const ethereumjsUtil = require('ethereumjs-util'); - const shouldBehaveLikeProxy = require('../Proxy.behaviour'); -const DummyImplementation = artifacts.require('DummyImplementation') -const DummyImplementationProxiable = artifacts.require('DummyImplementationProxiable') -const DummyImplementationV2Proxiable = artifacts.require('DummyImplementationV2Proxiable') const UUPSProxy = artifacts.require('UUPSProxy'); -const UUPS_UUID = '0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7'; - -function toChecksumAddress (address) { - return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0')); -} - contract('UUPSProxy', function (accounts) { - const [ proxyCreator ] = accounts; + const [proxyAdminOwner] = accounts; const createProxy = async function (implementation, _admin, initData, opts) { return UUPSProxy.new(implementation, initData, opts); }; - shouldBehaveLikeProxy(createProxy, undefined, proxyCreator, { - artefact: DummyImplementationProxiable, - slot: UUPS_UUID, - }); - - context('UUPS', function () { - beforeEach(async function () { - this.implementation = await DummyImplementation.new(); - this.implementationproxiable = await DummyImplementationProxiable.new(); - this.implementationproxiablev2 = await DummyImplementationV2Proxiable.new(); - const { address } = await createProxy(this.implementationproxiable.address, undefined, '0x', { from: proxyCreator }); - this.dummy = new DummyImplementationProxiable(address); - }); - - it('check uuid', async function() { - expect(await this.dummy.proxiableUUID()).to.be.equal(UUPS_UUID); - }); - - it('can update to proxiable', async function() { - await this.dummy.updateCodeAddress(this.implementationproxiablev2.address); - const implementation = toChecksumAddress((await web3.eth.getStorageAt(this.dummy.address, UUPS_UUID)).substr(-40)); - expect(implementation).to.be.equal(this.implementationproxiablev2.address); - }); - - it('does not deploy with non-proxiable', async function() { - await expectRevert.unspecified(createProxy(this.implementation.address, undefined, '0x', { from: proxyCreator })); - }); - - it('does not update to non-proxiable', async function() { - await expectRevert.unspecified(this.dummy.updateCodeAddress(this.implementation.address)); - }); - }); + shouldBehaveLikeProxy(createProxy, undefined, proxyAdminOwner); }); From aceb5007396b02e3c4771892113d79d17bf34a5c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 1 Mar 2021 17:34:10 +0100 Subject: [PATCH 4/7] proxy/ERC1967/ERC1967Proxy version --- .../mocks/DummyImplementationProxiable.sol | 18 ----- contracts/proxy/ERC1967/ERC1967Proxy.sol | 78 +++++++++++++++++++ .../TransparentUpgradeableProxy.sol | 6 +- contracts/proxy/uups/UUPS.sol | 50 ------------ contracts/proxy/uups/UUPSProxiable.sol | 16 ---- contracts/proxy/uups/UUPSProxy.sol | 42 ---------- .../ERC1967Proxy.test.js} | 6 +- .../TransparentUpgradeableProxy.behaviour.js | 2 +- test/proxy/uups/UUPSProxiable.test.js | 53 ------------- 9 files changed, 85 insertions(+), 186 deletions(-) delete mode 100644 contracts/mocks/DummyImplementationProxiable.sol create mode 100644 contracts/proxy/ERC1967/ERC1967Proxy.sol delete mode 100644 contracts/proxy/uups/UUPS.sol delete mode 100644 contracts/proxy/uups/UUPSProxiable.sol delete mode 100644 contracts/proxy/uups/UUPSProxy.sol rename test/proxy/{uups/UUPSProxy.test.js => ERC1967/ERC1967Proxy.test.js} (61%) delete mode 100644 test/proxy/uups/UUPSProxiable.test.js diff --git a/contracts/mocks/DummyImplementationProxiable.sol b/contracts/mocks/DummyImplementationProxiable.sol deleted file mode 100644 index eeee85b412f..00000000000 --- a/contracts/mocks/DummyImplementationProxiable.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./DummyImplementation.sol"; -import "../proxy/uups/UUPSProxiable.sol"; - -contract DummyImplementationProxiable is DummyImplementation, UUPSProxiable { - function updateCodeAddress(address newImplementation) public { - _updateCodeAddress(newImplementation); - } -} - -contract DummyImplementationV2Proxiable is DummyImplementationV2, UUPSProxiable { - function updateCodeAddress(address newImplementation) public { - _updateCodeAddress(newImplementation); - } -} diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol new file mode 100644 index 00000000000..5528e608dee --- /dev/null +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../Proxy.sol"; +import "../../utils/Address.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 { + /** + * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. + * + * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded + * function call, and allows initializating the storage of the proxy like a Solidity constructor. + */ + 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); + } + } + + /** + * @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 { + _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) + } + } +} diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 120762d0dbd..486c473de8d 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import "../uups/UUPSProxy.sol"; +import "../ERC1967/ERC1967Proxy.sol"; /** * @dev This contract implements a proxy that is upgradeable by an admin. @@ -25,12 +25,12 @@ import "../uups/UUPSProxy.sol"; * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. */ -contract TransparentUpgradeableProxy is UUPSProxy { +contract TransparentUpgradeableProxy is ERC1967Proxy { /** * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {UpgradeableProxy-constructor}. */ - constructor(address _logic, address admin_, bytes memory _data) payable UUPSProxy(_logic, _data) { + constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); _setAdmin(admin_); } diff --git a/contracts/proxy/uups/UUPS.sol b/contracts/proxy/uups/UUPS.sol deleted file mode 100644 index caad0ec006c..00000000000 --- a/contracts/proxy/uups/UUPS.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../../utils/Address.sol"; - -abstract contract UUPS { - /** - * @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 Emitted when the implementation is upgraded. - */ - event Upgraded(address indexed implementation); - - /** - * @dev Return the UUPS uuid. - */ - function _uuid() internal view virtual returns (bytes32) { - return _IMPLEMENTATION_SLOT; - } - - /** - * @dev Upgrades the proxy to a new implementation. - * - * Emits an {Upgraded} event. - */ - function _upgradeTo(address newImplementation) internal virtual { - _setImplementation(newImplementation); - emit Upgraded(newImplementation); - } - - /** - * @dev Stores a new address in the EIP1967 implementation slot. - */ - function _setImplementation(address newImplementation) private { - require(Address.isContract(newImplementation), "UUPS: new implementation is not a contract"); - - bytes32 slot = _IMPLEMENTATION_SLOT; - - // solhint-disable-next-line no-inline-assembly - assembly { - sstore(slot, newImplementation) - } - } -} diff --git a/contracts/proxy/uups/UUPSProxiable.sol b/contracts/proxy/uups/UUPSProxiable.sol deleted file mode 100644 index c2af91af540..00000000000 --- a/contracts/proxy/uups/UUPSProxiable.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./UUPS.sol"; - -abstract contract UUPSProxiable is UUPS { - function proxiableUUID() public view returns (bytes32) { - return _uuid(); - } - - function _updateCodeAddress(address newAddress) internal { - require(_uuid() == UUPSProxiable(newAddress).proxiableUUID(), "UUPS: new impelmentation is not compatible"); - _upgradeTo(newAddress); - } -} diff --git a/contracts/proxy/uups/UUPSProxy.sol b/contracts/proxy/uups/UUPSProxy.sol deleted file mode 100644 index 9529f35920f..00000000000 --- a/contracts/proxy/uups/UUPSProxy.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./UUPS.sol"; -import "../Proxy.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 UUPSProxy is Proxy, UUPS { - /** - * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. - * - * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded - * function call, and allows initializating the storage of the proxy like a Solidity constructor. - */ - constructor(address _logic, bytes memory _data) payable { - assert(_uuid() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); - _upgradeTo(_logic); - if(_data.length > 0) { - Address.functionDelegateCall(_logic, _data); - } - } - - /** - * @dev Returns the current implementation address. - */ - function _implementation() internal view virtual override returns (address impl) { - bytes32 slot = _uuid(); - // solhint-disable-next-line no-inline-assembly - assembly { - impl := sload(slot) - } - } -} diff --git a/test/proxy/uups/UUPSProxy.test.js b/test/proxy/ERC1967/ERC1967Proxy.test.js similarity index 61% rename from test/proxy/uups/UUPSProxy.test.js rename to test/proxy/ERC1967/ERC1967Proxy.test.js index a960c0d8a2b..22df960ffbd 100644 --- a/test/proxy/uups/UUPSProxy.test.js +++ b/test/proxy/ERC1967/ERC1967Proxy.test.js @@ -1,12 +1,12 @@ const shouldBehaveLikeProxy = require('../Proxy.behaviour'); -const UUPSProxy = artifacts.require('UUPSProxy'); +const ERC1967Proxy = artifacts.require('ERC1967Proxy'); -contract('UUPSProxy', function (accounts) { +contract('ERC1967Proxy', function (accounts) { const [proxyAdminOwner] = accounts; const createProxy = async function (implementation, _admin, initData, opts) { - return UUPSProxy.new(implementation, initData, opts); + return ERC1967Proxy.new(implementation, initData, opts); }; shouldBehaveLikeProxy(createProxy, undefined, proxyAdminOwner); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 2b66c00ee17..7c702fb3ff9 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -80,7 +80,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro it('reverts', async function () { await expectRevert( this.proxy.upgradeTo(ZERO_ADDRESS, { from }), - 'UUPS: new implementation is not a contract', + 'ERC1967Proxy: new implementation is not a contract', ); }); }); diff --git a/test/proxy/uups/UUPSProxiable.test.js b/test/proxy/uups/UUPSProxiable.test.js deleted file mode 100644 index e62c24e07b2..00000000000 --- a/test/proxy/uups/UUPSProxiable.test.js +++ /dev/null @@ -1,53 +0,0 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); -const ethereumjsUtil = require('ethereumjs-util'); - -const DummyImplementation = artifacts.require('DummyImplementation'); -const DummyImplementationProxiable = artifacts.require('DummyImplementationProxiable'); -const DummyImplementationV2Proxiable = artifacts.require('DummyImplementationV2Proxiable'); -const UUPSProxy = artifacts.require('UUPSProxy'); - -const UUPS_UUID = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'; - -function toChecksumAddress (address) { - return ethereumjsUtil.toChecksumAddress('0x' + address.replace(/^0x/, '').padStart(40, '0')); -} - -contract('UUPSProxiable', function (accounts) { - const [ proxyCreator ] = accounts; - - const createProxy = async function (implementation, _admin, initData, opts) { - return UUPSProxy.new(implementation, initData, opts); - }; - - context('UUPS', function () { - beforeEach(async function () { - this.impl = await DummyImplementation.new(); - this.implproxiable = await DummyImplementationProxiable.new(); - this.implproxiablev2 = await DummyImplementationV2Proxiable.new(); - const { address } = await createProxy(this.implproxiable.address, undefined, '0x', { from: proxyCreator }); - this.dummy = new DummyImplementationProxiable(address); - }); - - it('check uuid', async function () { - expect(await this.dummy.proxiableUUID()).to.be.equal(UUPS_UUID); - }); - - it('can update to proxiable', async function () { - await this.dummy.updateCodeAddress(this.implproxiablev2.address); - const implementation = toChecksumAddress((await web3.eth.getStorageAt( - this.dummy.address, - UUPS_UUID, - )).substr(-40)); - expect(implementation).to.be.equal(this.implproxiablev2.address); - }); - - it('does not deploy with non-proxiable', async function () { - this.skip(); // NOT SUPPORTED YET - await expectRevert.unspecified(createProxy(this.impl.address, undefined, '0x', { from: proxyCreator })); - }); - - it('does not update to non-proxiable', async function () { - await expectRevert.unspecified(this.dummy.updateCodeAddress(this.impl.address)); - }); - }); -}); From 6c298dedcfcc353fb84ce0264b301aa50316e1cc Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 1 Mar 2021 20:41:51 +0100 Subject: [PATCH 5/7] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c88a5c0e0c..daac446ce68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * `AccessControl`: removed enumerability by default for a more lightweight contract. It is now opt-in through `AccessControlEnumerable`. ([#2512](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2512)) * Meta Transactions: add `ERC2771Context` and a `MinimalForwarder` for meta-transactions. ([#2508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2508)) * Overall reorganisation of the contract folder to improve clarity and discoverability. ([#2503](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2503)) + * Rename `UpgradeableProxy` to `ERC1967Proxy`. ([#2547](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2547)) ### How to upgrade from 3.x From daad49993a4b329c5eda0f2d6cd88eeceaf0d070 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 1 Mar 2021 23:10:14 +0100 Subject: [PATCH 6/7] documentation for ERC1967Proxy --- contracts/proxy/README.adoc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/proxy/README.adoc b/contracts/proxy/README.adoc index ea6bf3a389d..50daf323924 100644 --- a/contracts/proxy/README.adoc +++ b/contracts/proxy/README.adoc @@ -7,7 +7,7 @@ This is a low-level set of contracts implementing different proxy patterns with The abstract {Proxy} contract implements the core delegation functionality. If the concrete proxies that we provide below are not suitable, we encourage building on top of this base contract since it contains an assembly block that may be hard to get right. -Upgradeability is implemented in the {UpgradeableProxy} contract, although it provides only 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. +{ERC1967Proxy} provides a simple, fully functionning, 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 <>. 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. @@ -19,7 +19,9 @@ CAUTION: Using upgradeable proxies correctly and securely is a difficult task th {{Proxy}} -{{UpgradeableProxy}} +== ERC1967 + +{{ERC1967Proxy}} == Transparent Proxy From 938e93b5bc64ba50144b21ee04a03abfdd18624b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 2 Mar 2021 09:20:57 +0100 Subject: [PATCH 7/7] Update contracts/proxy/README.adoc Co-authored-by: Francisco Giordano --- contracts/proxy/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/proxy/README.adoc b/contracts/proxy/README.adoc index 50daf323924..cb05da83fb2 100644 --- a/contracts/proxy/README.adoc +++ b/contracts/proxy/README.adoc @@ -7,7 +7,7 @@ This is a low-level set of contracts implementing different proxy patterns with The abstract {Proxy} contract implements the core delegation functionality. If the concrete proxies that we provide below are not suitable, we encourage building on top of this base contract since it contains an assembly block that may be hard to get right. -{ERC1967Proxy} provides a simple, fully functionning, 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. +{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 <>. 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.