From 7e955b1c715666cba02fcf2a3c42890f99ec7489 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Fri, 22 Sep 2023 19:00:06 -0400 Subject: [PATCH 01/14] Use upgradeToAndCall depending on interface version --- packages/core/src/call-optional-selector.ts | 22 +++++++++ packages/core/src/impl-address.ts | 29 ++++-------- packages/core/src/index.ts | 2 + .../src/upgrade-interface-version.test.ts | 34 ++++++++++++++ .../core/src/upgrade-interface-version.ts | 18 ++++++++ packages/core/src/validate/query.ts | 2 +- .../plugin-hardhat/contracts/Greeter50.sol | 22 +++++++++ .../plugin-hardhat/contracts/Greeter50V2.sol | 26 +++++++++++ .../contracts/utils/Proxiable50.sol | 46 +++++++++++++++++++ packages/plugin-hardhat/src/upgrade-proxy.ts | 32 ++++++++++--- .../plugin-hardhat/test/uups-happy-path-50.js | 18 ++++++++ .../test/uups-happy-path-with-call-50.js | 36 +++++++++++++++ 12 files changed, 259 insertions(+), 28 deletions(-) create mode 100644 packages/core/src/call-optional-selector.ts create mode 100644 packages/core/src/upgrade-interface-version.test.ts create mode 100644 packages/core/src/upgrade-interface-version.ts create mode 100644 packages/plugin-hardhat/contracts/Greeter50.sol create mode 100644 packages/plugin-hardhat/contracts/Greeter50V2.sol create mode 100644 packages/plugin-hardhat/contracts/utils/Proxiable50.sol create mode 100644 packages/plugin-hardhat/test/uups-happy-path-50.js create mode 100644 packages/plugin-hardhat/test/uups-happy-path-with-call-50.js diff --git a/packages/core/src/call-optional-selector.ts b/packages/core/src/call-optional-selector.ts new file mode 100644 index 000000000..fb1d86af8 --- /dev/null +++ b/packages/core/src/call-optional-selector.ts @@ -0,0 +1,22 @@ +import { keccak256 } from 'ethereumjs-util'; +import { call, EthereumProvider } from './provider'; + +export async function callOptionalSelector(provider: EthereumProvider, address: string, selector: string) { + const data = '0x' + keccak256(Buffer.from(selector)).toString('hex').slice(0, 8); + try { + return await call(provider, address, data); + } catch (e: any) { + if ( + !( + e.message.includes('function selector was not recognized') || + e.message.includes('invalid opcode') || + e.message.includes('revert') || + e.message.includes('execution error') + ) + ) { + throw e; + } else { + return undefined; + } + } +} diff --git a/packages/core/src/impl-address.ts b/packages/core/src/impl-address.ts index 426570702..6bc462543 100644 --- a/packages/core/src/impl-address.ts +++ b/packages/core/src/impl-address.ts @@ -1,12 +1,11 @@ -import { keccak256 } from 'ethereumjs-util'; import { - call, EIP1967BeaconNotFound, EIP1967ImplementationNotFound, getBeaconAddress, getImplementationAddress, UpgradesError, } from '.'; +import { callOptionalSelector } from './call-optional-selector'; import { EthereumProvider } from './provider'; import { parseAddress } from './utils/address'; @@ -24,27 +23,17 @@ export async function getImplementationAddressFromBeacon( provider: EthereumProvider, beaconAddress: string, ): Promise { - const implementationFunction = '0x' + keccak256(Buffer.from('implementation()')).toString('hex').slice(0, 8); - let result: string | undefined; - try { - const implAddress = await call(provider, beaconAddress, implementationFunction); - result = parseAddress(implAddress); - } catch (e: any) { - if ( - !( - e.message.includes('function selector was not recognized') || - e.message.includes('invalid opcode') || - e.message.includes('revert') || - e.message.includes('execution error') - ) - ) { - throw e; - } // otherwise fall through with no result + const impl = await callOptionalSelector(provider, beaconAddress, 'implementation()'); + let parsedImplAddress; + if (impl !== undefined) { + parsedImplAddress = parseAddress(impl); } - if (result === undefined) { + + if (parsedImplAddress === undefined) { throw new InvalidBeacon(`Contract at ${beaconAddress} doesn't look like a beacon`); + } else { + return parsedImplAddress; } - return result; } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 072e8a7b1..a66dc27dd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -57,3 +57,5 @@ export { } from './usage-error'; export { ValidateUpgradeSafetyOptions, validateUpgradeSafety, ProjectReport, ReferenceContractNotFound } from './cli'; + +export { getUpgradeInterfaceVersion } from './upgrade-interface-version'; diff --git a/packages/core/src/upgrade-interface-version.test.ts b/packages/core/src/upgrade-interface-version.test.ts new file mode 100644 index 000000000..f0a9a7d78 --- /dev/null +++ b/packages/core/src/upgrade-interface-version.test.ts @@ -0,0 +1,34 @@ +import test from 'ava'; +import { EthereumProvider } from './provider'; +import { getUpgradeInterfaceVersion } from './upgrade-interface-version'; + +const hash = '0x1234'; + +function makeProviderReturning(result: unknown): EthereumProvider { + return { send: (_method: string, _params: unknown[]) => Promise.resolve(result) } as EthereumProvider; +} + +function makeProviderError(msg: string): EthereumProvider { + return { + send: (_method: string, _params: unknown[]) => { + throw new Error(msg); + }, + } as EthereumProvider; +} + +test('getUpgradeInterfaceVersion returns version', async t => { + const provider = makeProviderReturning('5.0.0'); + t.is(await getUpgradeInterfaceVersion(provider, hash), '5.0.0'); +}); + +test('getUpgradeInterfaceVersion throws unrelated error', async t => { + const provider = makeProviderError('unrelated error'); + await t.throwsAsync(() => getUpgradeInterfaceVersion(provider, hash), { message: 'unrelated error' }); +}); + +test('getUpgradeInterfaceVersion returns undefined for invalid selector', async t => { + const provider = makeProviderError( + `Transaction reverted: function selector was not recognized and there's no fallback function`, + ); + t.is(await getUpgradeInterfaceVersion(provider, hash), undefined); +}); diff --git a/packages/core/src/upgrade-interface-version.ts b/packages/core/src/upgrade-interface-version.ts new file mode 100644 index 000000000..034ad5e3a --- /dev/null +++ b/packages/core/src/upgrade-interface-version.ts @@ -0,0 +1,18 @@ +import { callOptionalSelector } from './call-optional-selector'; +import { EthereumProvider } from './provider'; + +import { AbiCoder } from 'ethers'; + +export async function getUpgradeInterfaceVersion( + provider: EthereumProvider, + address: string, +): Promise { + const version = await callOptionalSelector(provider, address, 'UPGRADE_INTERFACE_VERSION()'); + if (version !== undefined) { + const abiCoder = new AbiCoder(); + const decodedVersion = abiCoder.decode(['string'], version); + return decodedVersion[0]; + } else { + return undefined; + } +} diff --git a/packages/core/src/validate/query.ts b/packages/core/src/validate/query.ts index dc73c7dd5..51c005668 100644 --- a/packages/core/src/validate/query.ts +++ b/packages/core/src/validate/query.ts @@ -192,7 +192,7 @@ export function isUpgradeSafe(data: ValidationData, version: Version): boolean { export function inferUUPS(runValidation: ValidationRunData, fullContractName: string): boolean { const methods = getAllMethods(runValidation, fullContractName); - return methods.includes(upgradeToSignature); + return methods.includes(upgradeToSignature) || methods.includes(upgradeToAndCallSignature); } export function inferProxyKind(data: ValidationData, version: Version): ProxyDeployment['kind'] { diff --git a/packages/plugin-hardhat/contracts/Greeter50.sol b/packages/plugin-hardhat/contracts/Greeter50.sol new file mode 100644 index 000000000..828d6be1d --- /dev/null +++ b/packages/plugin-hardhat/contracts/Greeter50.sol @@ -0,0 +1,22 @@ +pragma solidity >= 0.4.22 <0.8.0; + +contract Greeter50 { + + string greeting; + + function initialize(string memory _greeting) public { + greeting = _greeting; + } + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } + +} + +import "./utils/Proxiable50.sol"; +contract Greeter50Proxiable is Greeter50, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/Greeter50V2.sol b/packages/plugin-hardhat/contracts/Greeter50V2.sol new file mode 100644 index 000000000..6e5cd1474 --- /dev/null +++ b/packages/plugin-hardhat/contracts/Greeter50V2.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.5.1; + +contract Greeter50V2 { + + string greeting; + + function initialize(string memory _greeting) public { + greeting = _greeting; + } + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } + + function resetGreeting() public { + greeting = "Hello World"; + } + +} + +import "./utils/Proxiable50.sol"; +contract Greeter50V2Proxiable is Greeter50V2, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/utils/Proxiable50.sol b/packages/plugin-hardhat/contracts/utils/Proxiable50.sol new file mode 100644 index 000000000..e6162df88 --- /dev/null +++ b/packages/plugin-hardhat/contracts/utils/Proxiable50.sol @@ -0,0 +1,46 @@ +pragma solidity >= 0.4.22 <0.8.0; + +// This contract is for testing only, it is not safe for use in production. + +contract Proxiable50 { + bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + + function upgradeToAndCall(address newImplementation, bytes calldata data) external { + _setImplementation(newImplementation); + if (data.length > 0) { + /** + * Using address(this).call is dangerous as the call can impersonate the proxy being upgraded. + * a better option is to use a delegate call with an oz-upgrades-unsafe-allow, but this is not + * supported by the early version of solidity used here. + * + * /// @custom:oz-upgrades-unsafe-allow delegatecall + * (bool success, ) = newImplementation.delegatecall(data); + * + * Note that using delegate call can make your implementation contract vulnerable if this function + * is not protected with the `onlyProxy` modifier. Again, This contract is for testing only, it is + * not safe for use in production. Instead, use the `UUPSUpgradeable` contract available in + * @openzeppelin/contracts-upgradeable + */ + (bool success, ) = address(this).call(data); + require(success, "upgrade call reverted"); + } else { + _checkNonPayable(); + } + } + + function _checkNonPayable() private { + if (msg.value > 0) { + revert('non-payable upgrade call'); + } + } + + function _setImplementation(address newImplementation) private { + bytes32 slot = _IMPLEMENTATION_SLOT; + // solhint-disable-next-line no-inline-assembly + assembly { + sstore(slot, newImplementation) + } + } +} diff --git a/packages/plugin-hardhat/src/upgrade-proxy.ts b/packages/plugin-hardhat/src/upgrade-proxy.ts index 4084a299d..bcdfbc3c6 100644 --- a/packages/plugin-hardhat/src/upgrade-proxy.ts +++ b/packages/plugin-hardhat/src/upgrade-proxy.ts @@ -1,7 +1,7 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import type { ethers, ContractFactory, Contract, Signer } from 'ethers'; -import { getAdminAddress, getCode, isEmptySlot } from '@openzeppelin/upgrades-core'; +import { getAdminAddress, getCode, getUpgradeInterfaceVersion, isEmptySlot } from '@openzeppelin/upgrades-core'; import { UpgradeProxyOptions, @@ -54,17 +54,35 @@ export function makeUpgradeProxy(hre: HardhatRuntimeEnvironment, defenderModule: const ITransparentUpgradeableProxyFactory = await getITransparentUpgradeableProxyFactory(hre, signer); const proxy = attach(ITransparentUpgradeableProxyFactory, proxyAddress); - return (nextImpl, call) => - call ? proxy.upgradeToAndCall(nextImpl, call, ...overrides) : proxy.upgradeTo(nextImpl, ...overrides); + const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, proxyAddress); + + return (nextImpl, call) => { + if (upgradeInterfaceVersion === undefined) { + return call ? proxy.upgradeToAndCall(nextImpl, call, ...overrides) : proxy.upgradeTo(nextImpl, ...overrides); + } else if (upgradeInterfaceVersion === '5.0.0') { + return proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides); + } else { + throw new Error(`Unknown upgrade interface version ${upgradeInterfaceVersion}`); + } + }; } else { // Admin contract: redirect upgrade call through it const AdminFactory = await getProxyAdminFactory(hre, signer); const admin = attach(AdminFactory, adminAddress); - return (nextImpl, call) => - call - ? admin.upgradeAndCall(proxyAddress, nextImpl, call, ...overrides) - : admin.upgrade(proxyAddress, nextImpl, ...overrides); + const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, adminAddress); + + return (nextImpl, call) => { + if (upgradeInterfaceVersion === undefined) { + return call + ? admin.upgradeAndCall(proxyAddress, nextImpl, call, ...overrides) + : admin.upgrade(proxyAddress, nextImpl, ...overrides); + } else if (upgradeInterfaceVersion === '5.0.0') { + return admin.upgradeAndCall(proxyAddress, nextImpl, call ?? '0x', ...overrides); + } else { + throw new Error(`Unknown upgrade interface version ${upgradeInterfaceVersion}`); + } + }; } } } diff --git a/packages/plugin-hardhat/test/uups-happy-path-50.js b/packages/plugin-hardhat/test/uups-happy-path-50.js new file mode 100644 index 000000000..389f3648c --- /dev/null +++ b/packages/plugin-hardhat/test/uups-happy-path-50.js @@ -0,0 +1,18 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter50Proxiable'); + t.context.GreeterV2 = await ethers.getContractFactory('Greeter50V2Proxiable'); +}); + +test('happy path', async t => { + const { Greeter, GreeterV2 } = t.context; + + const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'uups' }); + + const greeter2 = await upgrades.upgradeProxy(greeter, GreeterV2); + await greeter2.waitForDeployment(); + await greeter2.resetGreeting(); +}); diff --git a/packages/plugin-hardhat/test/uups-happy-path-with-call-50.js b/packages/plugin-hardhat/test/uups-happy-path-with-call-50.js new file mode 100644 index 000000000..5e9f7b79a --- /dev/null +++ b/packages/plugin-hardhat/test/uups-happy-path-with-call-50.js @@ -0,0 +1,36 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter50Proxiable'); + t.context.GreeterV2 = await ethers.getContractFactory('Greeter50V2Proxiable'); +}); + +test('happy path - call with args', async t => { + const { Greeter, GreeterV2 } = t.context; + + const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'uups' }); + + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + await upgrades.upgradeProxy(greeter, GreeterV2, { + call: { fn: 'setGreeting', args: ['Called during upgrade'] }, + }); + + t.is(await greeter.greet(), 'Called during upgrade'); +}); + +test('happy path - call without args', async t => { + const { Greeter, GreeterV2 } = t.context; + + const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'uups' }); + + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + await upgrades.upgradeProxy(greeter, GreeterV2, { + call: 'resetGreeting', + }); + + t.is(await greeter.greet(), 'Hello World'); +}); From 882a5f8670228a4b0cabeab0a85272aa122fced6 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Sat, 23 Sep 2023 00:53:23 -0400 Subject: [PATCH 02/14] Decode UPGRADE_INTERFACE_VERSION string directly --- .../src/upgrade-interface-version.test.ts | 5 +++- .../core/src/upgrade-interface-version.ts | 23 +++++++++++++------ packages/plugin-hardhat/src/upgrade-proxy.ts | 8 +++++-- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/core/src/upgrade-interface-version.test.ts b/packages/core/src/upgrade-interface-version.test.ts index f0a9a7d78..a0986dec2 100644 --- a/packages/core/src/upgrade-interface-version.test.ts +++ b/packages/core/src/upgrade-interface-version.test.ts @@ -17,7 +17,10 @@ function makeProviderError(msg: string): EthereumProvider { } test('getUpgradeInterfaceVersion returns version', async t => { - const provider = makeProviderReturning('5.0.0'); + // abi encoding of '5.0.0' + const provider = makeProviderReturning( + '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005352e302e30000000000000000000000000000000000000000000000000000000', + ); t.is(await getUpgradeInterfaceVersion(provider, hash), '5.0.0'); }); diff --git a/packages/core/src/upgrade-interface-version.ts b/packages/core/src/upgrade-interface-version.ts index 034ad5e3a..c06392375 100644 --- a/packages/core/src/upgrade-interface-version.ts +++ b/packages/core/src/upgrade-interface-version.ts @@ -1,17 +1,26 @@ import { callOptionalSelector } from './call-optional-selector'; import { EthereumProvider } from './provider'; -import { AbiCoder } from 'ethers'; - export async function getUpgradeInterfaceVersion( provider: EthereumProvider, address: string, ): Promise { - const version = await callOptionalSelector(provider, address, 'UPGRADE_INTERFACE_VERSION()'); - if (version !== undefined) { - const abiCoder = new AbiCoder(); - const decodedVersion = abiCoder.decode(['string'], version); - return decodedVersion[0]; + const encodedVersion = await callOptionalSelector(provider, address, 'UPGRADE_INTERFACE_VERSION()'); + if (encodedVersion !== undefined) { + // Encoded string + const buf = Buffer.from(encodedVersion.replace(/^0x/, ''), 'hex'); + + // The first 32 bytes represent the offset, which should be 32 for a string + const offset = parseInt(buf.slice(0, 32).toString('hex'), 16); + if (offset !== 32) { + throw new Error(`Unexpected type for UPGRADE_INTERFACE_VERSION at address ${address}. Expected a string`); + } + + // The next 32 bytes represent the length of the string + const length = parseInt(buf.slice(32, 64).toString('hex'), 16); + + // The rest is the string itself + return buf.slice(64, 64 + length).toString('utf8'); } else { return undefined; } diff --git a/packages/plugin-hardhat/src/upgrade-proxy.ts b/packages/plugin-hardhat/src/upgrade-proxy.ts index bcdfbc3c6..cc40fc539 100644 --- a/packages/plugin-hardhat/src/upgrade-proxy.ts +++ b/packages/plugin-hardhat/src/upgrade-proxy.ts @@ -62,7 +62,9 @@ export function makeUpgradeProxy(hre: HardhatRuntimeEnvironment, defenderModule: } else if (upgradeInterfaceVersion === '5.0.0') { return proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides); } else { - throw new Error(`Unknown upgrade interface version ${upgradeInterfaceVersion}`); + throw new Error( + `Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy at ${proxyAddress}. Expected 5.0.0`, + ); } }; } else { @@ -80,7 +82,9 @@ export function makeUpgradeProxy(hre: HardhatRuntimeEnvironment, defenderModule: } else if (upgradeInterfaceVersion === '5.0.0') { return admin.upgradeAndCall(proxyAddress, nextImpl, call ?? '0x', ...overrides); } else { - throw new Error(`Unknown upgrade interface version ${upgradeInterfaceVersion}`); + throw new Error( + `Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy admin at ${adminAddress}. Expected 5.0.0`, + ); } }; } From 30d54a4b30072583b41d94220b7e3056a240f0de Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Sat, 23 Sep 2023 01:11:58 -0400 Subject: [PATCH 03/14] Add changelogs, bump versions --- packages/core/CHANGELOG.md | 4 ++++ packages/core/package.json | 2 +- packages/plugin-hardhat/CHANGELOG.md | 5 +++++ packages/plugin-hardhat/package.json | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0aecc420d..71a3aecc9 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Support getting `UPGRADE_INTERFACE_VERSION`, fix inferring of UUPS proxies with `upgradeToAndCall`. ([883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) + ## 1.29.0 (2023-09-19) - Support implementations with upgradeTo or upgradeToAndCall. ([#880](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/880)) diff --git a/packages/core/package.json b/packages/core/package.json index b194e9627..950d15d16 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/upgrades-core", - "version": "1.29.0", + "version": "1.30.0", "description": "", "repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/core", "license": "MIT", diff --git a/packages/plugin-hardhat/CHANGELOG.md b/packages/plugin-hardhat/CHANGELOG.md index 0a911ff22..76855f6cd 100644 --- a/packages/plugin-hardhat/CHANGELOG.md +++ b/packages/plugin-hardhat/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- Support UUPS implementations that use OpenZeppelin Contracts 5.0. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) + - Note that deploying or importing version 5.0 proxies are not supported yet. + ## 2.2.1 (2023-08-18) - Allow using proxy with different admin address than manifest. ([#859](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/859)) diff --git a/packages/plugin-hardhat/package.json b/packages/plugin-hardhat/package.json index 61b40a6d9..1980d13d7 100644 --- a/packages/plugin-hardhat/package.json +++ b/packages/plugin-hardhat/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/hardhat-upgrades", - "version": "2.2.1", + "version": "2.3.0", "description": "", "repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/plugin-hardhat", "license": "MIT", @@ -38,7 +38,7 @@ "@openzeppelin/defender-admin-client": "^1.48.0", "@openzeppelin/defender-base-client": "^1.48.0", "@openzeppelin/platform-deploy-client": "^0.10.0", - "@openzeppelin/upgrades-core": "^1.27.0", + "@openzeppelin/upgrades-core": "^1.30.0", "chalk": "^4.1.0", "debug": "^4.1.1", "ethereumjs-util": "^7.1.5", From a22eccd2fe26b926805bdca2eaaa57dbf73ab4ff Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Sat, 23 Sep 2023 01:32:02 -0400 Subject: [PATCH 04/14] Use more specific changelog message --- packages/plugin-hardhat/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/plugin-hardhat/CHANGELOG.md b/packages/plugin-hardhat/CHANGELOG.md index 76855f6cd..bbb7d8944 100644 --- a/packages/plugin-hardhat/CHANGELOG.md +++ b/packages/plugin-hardhat/CHANGELOG.md @@ -2,8 +2,7 @@ ## Unreleased -- Support UUPS implementations that use OpenZeppelin Contracts 5.0. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) - - Note that deploying or importing version 5.0 proxies are not supported yet. +- Enable upgrading UUPS implementations depending on upgrade interface version. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) ## 2.2.1 (2023-08-18) From 2869e2afa7a523c2cc0965497c898d62369982d0 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Mon, 25 Sep 2023 16:04:30 -0400 Subject: [PATCH 05/14] Simplify test contracts --- .../plugin-hardhat/contracts/Greeter50.sol | 22 ---------------- .../contracts/Greeter50Proxiable.sol | 6 +++++ .../contracts/Greeter50ProxiableV2.sol | 6 +++++ .../plugin-hardhat/contracts/Greeter50V2.sol | 26 ------------------- 4 files changed, 12 insertions(+), 48 deletions(-) delete mode 100644 packages/plugin-hardhat/contracts/Greeter50.sol create mode 100644 packages/plugin-hardhat/contracts/Greeter50Proxiable.sol create mode 100644 packages/plugin-hardhat/contracts/Greeter50ProxiableV2.sol delete mode 100644 packages/plugin-hardhat/contracts/Greeter50V2.sol diff --git a/packages/plugin-hardhat/contracts/Greeter50.sol b/packages/plugin-hardhat/contracts/Greeter50.sol deleted file mode 100644 index 828d6be1d..000000000 --- a/packages/plugin-hardhat/contracts/Greeter50.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity >= 0.4.22 <0.8.0; - -contract Greeter50 { - - string greeting; - - function initialize(string memory _greeting) public { - greeting = _greeting; - } - - function greet() public view returns (string memory) { - return greeting; - } - - function setGreeting(string memory _greeting) public { - greeting = _greeting; - } - -} - -import "./utils/Proxiable50.sol"; -contract Greeter50Proxiable is Greeter50, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/Greeter50Proxiable.sol b/packages/plugin-hardhat/contracts/Greeter50Proxiable.sol new file mode 100644 index 000000000..6300f68da --- /dev/null +++ b/packages/plugin-hardhat/contracts/Greeter50Proxiable.sol @@ -0,0 +1,6 @@ +pragma solidity >= 0.4.22 <0.8.0; + +import "./Greeter.sol"; +import "./utils/Proxiable50.sol"; + +contract Greeter50Proxiable is Greeter, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/Greeter50ProxiableV2.sol b/packages/plugin-hardhat/contracts/Greeter50ProxiableV2.sol new file mode 100644 index 000000000..e713b381f --- /dev/null +++ b/packages/plugin-hardhat/contracts/Greeter50ProxiableV2.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.1; + +import "./GreeterV2.sol"; +import "./utils/Proxiable50.sol"; + +contract Greeter50V2Proxiable is GreeterV2, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/Greeter50V2.sol b/packages/plugin-hardhat/contracts/Greeter50V2.sol deleted file mode 100644 index 6e5cd1474..000000000 --- a/packages/plugin-hardhat/contracts/Greeter50V2.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity ^0.5.1; - -contract Greeter50V2 { - - string greeting; - - function initialize(string memory _greeting) public { - greeting = _greeting; - } - - function greet() public view returns (string memory) { - return greeting; - } - - function setGreeting(string memory _greeting) public { - greeting = _greeting; - } - - function resetGreeting() public { - greeting = "Hello World"; - } - -} - -import "./utils/Proxiable50.sol"; -contract Greeter50V2Proxiable is Greeter50V2, Proxiable50 {} From 167e1fd4a805d24c52259ef8d798979b9a3c1a26 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Mon, 25 Sep 2023 16:46:33 -0400 Subject: [PATCH 06/14] Add tests for importing 5.0 proxies --- ...oxiableV2.sol => Greeter50V2Proxiable.sol} | 0 .../contracts/Greeter50V3Proxiable.sol | 6 + .../plugin-hardhat/contracts/Import50.sol | 8 + packages/plugin-hardhat/hardhat.config.js | 3 + packages/plugin-hardhat/package.json | 1 + packages/plugin-hardhat/test/import-50.js | 369 ++++++++++++++++++ yarn.lock | 5 + 7 files changed, 392 insertions(+) rename packages/plugin-hardhat/contracts/{Greeter50ProxiableV2.sol => Greeter50V2Proxiable.sol} (100%) create mode 100644 packages/plugin-hardhat/contracts/Greeter50V3Proxiable.sol create mode 100644 packages/plugin-hardhat/contracts/Import50.sol create mode 100644 packages/plugin-hardhat/test/import-50.js diff --git a/packages/plugin-hardhat/contracts/Greeter50ProxiableV2.sol b/packages/plugin-hardhat/contracts/Greeter50V2Proxiable.sol similarity index 100% rename from packages/plugin-hardhat/contracts/Greeter50ProxiableV2.sol rename to packages/plugin-hardhat/contracts/Greeter50V2Proxiable.sol diff --git a/packages/plugin-hardhat/contracts/Greeter50V3Proxiable.sol b/packages/plugin-hardhat/contracts/Greeter50V3Proxiable.sol new file mode 100644 index 000000000..efb648426 --- /dev/null +++ b/packages/plugin-hardhat/contracts/Greeter50V3Proxiable.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.1; + +import "./GreeterV3.sol"; +import "./utils/Proxiable50.sol"; + +contract Greeter50V3Proxiable is GreeterV3, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/Import50.sol b/packages/plugin-hardhat/contracts/Import50.sol new file mode 100644 index 000000000..fe639347c --- /dev/null +++ b/packages/plugin-hardhat/contracts/Import50.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts-5.0/proxy/beacon/BeaconProxy.sol"; +import "@openzeppelin/contracts-5.0/proxy/beacon/UpgradeableBeacon.sol"; +import "@openzeppelin/contracts-5.0/proxy/ERC1967/ERC1967Proxy.sol"; +import "@openzeppelin/contracts-5.0/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts-5.0/proxy/transparent/ProxyAdmin.sol"; \ No newline at end of file diff --git a/packages/plugin-hardhat/hardhat.config.js b/packages/plugin-hardhat/hardhat.config.js index 98a1e9473..96239448e 100644 --- a/packages/plugin-hardhat/hardhat.config.js +++ b/packages/plugin-hardhat/hardhat.config.js @@ -12,6 +12,9 @@ const override = { module.exports = { solidity: { compilers: [ + { + version: '0.8.20', + }, { version: '0.8.9', }, diff --git a/packages/plugin-hardhat/package.json b/packages/plugin-hardhat/package.json index 1980d13d7..360c19e05 100644 --- a/packages/plugin-hardhat/package.json +++ b/packages/plugin-hardhat/package.json @@ -25,6 +25,7 @@ "@nomicfoundation/hardhat-verify": "^1.1.0", "@openzeppelin/contracts": "4.8.3", "@openzeppelin/contracts-upgradeable": "4.8.3", + "@openzeppelin/contracts-5.0": "npm:@openzeppelin/contracts@^5.0.0-rc.0", "@types/mocha": "^7.0.2", "ava": "^5.0.0", "fgbg": "^0.1.4", diff --git a/packages/plugin-hardhat/test/import-50.js b/packages/plugin-hardhat/test/import-50.js new file mode 100644 index 000000000..899fad9bd --- /dev/null +++ b/packages/plugin-hardhat/test/import-50.js @@ -0,0 +1,369 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +const ProxyAdmin = require('../artifacts/@openzeppelin/contracts-5.0/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'); +const TransparentUpgradableProxy = require('../artifacts/@openzeppelin/contracts-5.0/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'); + +const ERC1967Proxy = require('../artifacts/@openzeppelin/contracts-5.0/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'); + +const BeaconProxy = require('../artifacts/@openzeppelin/contracts-5.0/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'); +const UpgradableBeacon = require('../artifacts/@openzeppelin/contracts-5.0/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter'); + t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2'); + t.context.GreeterV3 = await ethers.getContractFactory('GreeterV3'); + t.context.GreeterProxiable = await ethers.getContractFactory('Greeter50Proxiable'); + t.context.GreeterV2Proxiable = await ethers.getContractFactory('Greeter50V2Proxiable'); + t.context.GreeterV3Proxiable = await ethers.getContractFactory('GreeterV3Proxiable'); + t.context.CustomProxy = await ethers.getContractFactory('CustomProxy'); + t.context.CustomProxyWithAdmin = await ethers.getContractFactory('CustomProxyWithAdmin'); + + t.context.ProxyAdmin = await ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode); + t.context.TransparentUpgradableProxy = await ethers.getContractFactory( + TransparentUpgradableProxy.abi, + TransparentUpgradableProxy.bytecode, + ); + + t.context.ERC1967Proxy = await ethers.getContractFactory(ERC1967Proxy.abi, ERC1967Proxy.bytecode); + + t.context.BeaconProxy = await ethers.getContractFactory(BeaconProxy.abi, BeaconProxy.bytecode); + t.context.UpgradableBeacon = await ethers.getContractFactory(UpgradableBeacon.abi, UpgradableBeacon.bytecode); +}); + +function getInitializerData(contractInterface, args) { + const initializer = 'initialize'; + const fragment = contractInterface.getFunction(initializer); + return contractInterface.encodeFunctionData(fragment, args); +} + +const REQUESTED_UPGRADE_WRONG_KIND = 'Requested an upgrade of kind uups but proxy is transparent'; + +test('implementation happy path', async t => { + const { GreeterProxiable } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + + const contract = await upgrades.forceImport(await impl.getAddress(), GreeterProxiable); + t.is(await impl.getAddress(), await contract.getAddress()); + t.is('', await contract.greet()); + + const greeter = await upgrades.deployProxy(GreeterProxiable, ['Hello, Hardhat!'], { + useDeployedImplementation: true, + }); + t.is(await greeter.greet(), 'Hello, Hardhat!'); +}); + +test('no contract', async t => { + const { GreeterProxiable } = t.context; + + const e = await t.throwsAsync(() => + upgrades.forceImport('0x0000000000000000000000000000000000000001', GreeterProxiable), + ); + t.true(e.message.startsWith('No contract at address 0x0000000000000000000000000000000000000001'), e.message); +}); + +test('transparent happy path', async t => { + const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + const owner = (await ethers.getSigners())[0]; + + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(owner); + await admin.waitForDeployment(); + const proxy = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + owner, + getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + const greeter2 = await upgrades.upgradeProxy(greeter, GreeterV2); + await greeter2.waitForDeployment(); + t.is(await greeter2.greet(), 'Hello, Hardhat!'); + await greeter2.resetGreeting(); + t.is(await greeter2.greet(), 'Hello World'); +}); + +test('uups happy path', async t => { + const { GreeterProxiable, GreeterV2Proxiable, ERC1967Proxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + const greeter2 = await upgrades.upgradeProxy(greeter, GreeterV2Proxiable); + await greeter2.waitForDeployment(); + t.is(await greeter2.greet(), 'Hello, Hardhat!'); + await greeter2.resetGreeting(); + t.is(await greeter2.greet(), 'Hello World'); +}); + +test('beacon proxy happy path', async t => { + const { Greeter, GreeterV2, UpgradableBeacon, BeaconProxy } = t.context; + + const owner = (await ethers.getSigners())[0]; + + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const beacon = await UpgradableBeacon.deploy(await impl.getAddress(), owner); + await beacon.waitForDeployment(); + const proxy = await BeaconProxy.deploy( + await beacon.getAddress(), + getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + await upgrades.upgradeBeacon(beacon, GreeterV2); + const greeter2 = GreeterV2.attach(await greeter.getAddress()); + await greeter2.waitForDeployment(); + t.is(await greeter2.greet(), 'Hello, Hardhat!'); + await greeter2.resetGreeting(); + t.is(await greeter2.greet(), 'Hello World'); +}); + +test('beacon happy path', async t => { + const { Greeter, GreeterV2, UpgradableBeacon } = t.context; + + const owner = (await ethers.getSigners())[0]; + + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const beacon = await UpgradableBeacon.deploy(await impl.getAddress(), owner); + await beacon.waitForDeployment(); + + const beaconImported = await upgrades.forceImport(await beacon.getAddress(), Greeter); + t.is(await beaconImported.implementation(), await impl.getAddress()); + + await upgrades.upgradeBeacon(beacon, GreeterV2); +}); + +test('import proxy using contract instance', async t => { + const { GreeterProxiable, GreeterV2Proxiable, ERC1967Proxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(proxy, GreeterProxiable); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + const greeter2 = await upgrades.upgradeProxy(greeter, GreeterV2Proxiable); + await greeter2.waitForDeployment(); + t.is(await greeter2.greet(), 'Hello, Hardhat!'); + await greeter2.resetGreeting(); + t.is(await greeter2.greet(), 'Hello World'); +}); + +test('wrong kind', async t => { + const { GreeterProxiable, GreeterV2Proxiable, ERC1967Proxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + // specify wrong kind + const greeter = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable, { kind: 'transparent' }); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + // an error is expected since the user force imported the wrong kind + const e = await t.throwsAsync(() => upgrades.upgradeProxy(greeter, GreeterV2Proxiable)); + t.true(e.message.startsWith(REQUESTED_UPGRADE_WRONG_KIND), e.message); +}); + +test('import custom UUPS proxy', async t => { + const { GreeterProxiable, GreeterV2Proxiable, CustomProxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await CustomProxy.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + await upgrades.upgradeProxy(greeter, GreeterV2Proxiable); +}); + +test('import custom UUPS proxy with admin', async t => { + const { GreeterProxiable, GreeterV2Proxiable, CustomProxyWithAdmin } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await CustomProxyWithAdmin.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + await upgrades.upgradeProxy(greeter, GreeterV2Proxiable); +}); + +test('wrong implementation', async t => { + const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + const owner = (await ethers.getSigners())[0]; + + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(owner); + await admin.waitForDeployment(); + const proxy = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + owner, + getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), GreeterV2); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + // since this is the wrong impl, expect it to have an error if using a non-existent function + const e = await t.throwsAsync(() => greeter.resetGreeting()); + t.true(e.message.includes('Transaction reverted'), e.message); +}); + +test('multiple identical implementations', async t => { + const { GreeterProxiable, GreeterV2Proxiable, ERC1967Proxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const impl2 = await GreeterProxiable.deploy(); + await impl2.waitForDeployment(); + const proxy2 = await ERC1967Proxy.deploy( + await impl2.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat 2!']), + ); + await proxy2.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + const greeterUpgraded = await upgrades.upgradeProxy(greeter, GreeterV2Proxiable); + t.is(await greeterUpgraded.greet(), 'Hello, Hardhat!'); + + const greeter2 = await upgrades.forceImport(await proxy2.getAddress(), GreeterProxiable); + const greeter2Upgraded = await upgrades.upgradeProxy(greeter2, GreeterV2Proxiable); + t.is(await greeter2Upgraded.greet(), 'Hello, Hardhat 2!'); +}); + +test('same implementation', async t => { + const { GreeterProxiable, ERC1967Proxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + const proxy2 = await ERC1967Proxy.deploy( + await impl.getAddress(), + getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat 2!']), + ); + await proxy2.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + const greeter2 = await upgrades.forceImport(await proxy2.getAddress(), GreeterProxiable); + + const implAddr1 = await upgrades.erc1967.getImplementationAddress(await greeter.getAddress()); + const implAddr2 = await upgrades.erc1967.getImplementationAddress(await greeter2.getAddress()); + t.is(implAddr2, implAddr1); +}); + +test('import transparents with different admin', async t => { + const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + const owner = (await ethers.getSigners())[0]; + + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(owner); + await admin.waitForDeployment(); + const proxy = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + owner, + getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const owner2 = (await ethers.getSigners())[1]; + + const admin2 = await ProxyAdmin.deploy(owner2); + await admin2.waitForDeployment(); + const proxy2 = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + owner2, + getInitializerData(Greeter.interface, ['Hello, Hardhat 2!']), + ); + await proxy2.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); + const greeter2 = await upgrades.forceImport(await proxy2.getAddress(), Greeter); + + t.not( + await upgrades.erc1967.getAdminAddress(await greeter2.getAddress()), + await upgrades.erc1967.getAdminAddress(await greeter.getAddress()), + ); + + // proxy with a different admin can be imported + const proxyAddress = await proxy.getAddress(); + await upgrades.upgradeProxy(proxyAddress, GreeterV2); +}); + +test('import transparent then upgrade with call', async t => { + const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + const owner = (await ethers.getSigners())[0]; + + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(owner); + await admin.waitForDeployment(); + const proxy = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + owner, + getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); + t.is(await greeter.greet(), 'Hello, Hardhat!'); + + await upgrades.upgradeProxy(greeter, GreeterV2, { call: 'resetGreeting' }); + t.is(await greeter.greet(), 'Hello World'); +}); diff --git a/yarn.lock b/yarn.lock index 6411ef2cd..b6f389021 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2318,6 +2318,11 @@ dependencies: "@octokit/openapi-types" "^18.0.0" +"@openzeppelin/contracts-5.0@npm:@openzeppelin/contracts@^5.0.0-rc.0": + version "5.0.0-rc.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0-rc.0.tgz#801345b25e27c8a27e6075b508fd9e17cc303e8d" + integrity sha512-OvYXXB1EshHue8IBakqkYCglk3Yh/NaP9HUDeoONXBmTCBD/4Oo/dU84ZJ19CG6M+lEy55I7N30xNGTT69396Q== + "@openzeppelin/contracts-upgradeable@4.8.3": version "4.8.3" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.3.tgz#6b076a7b751811b90fe3a172a7faeaa603e13a3f" From bb3b58bdfd03616289d41ee7e5e80d095851baf0 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Mon, 25 Sep 2023 16:59:42 -0400 Subject: [PATCH 07/14] Add note for changeProxyAdmin --- docs/modules/ROOT/pages/api-hardhat-upgrades.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc b/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc index 408720341..04c128c1d 100644 --- a/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc +++ b/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc @@ -616,6 +616,8 @@ async function changeProxyAdmin( Changes the admin for a specific proxy. +NOTE: This function is not supported with transparent proxies from OpenZeppelin Contracts 5.x, since those use immutable values for their admin address. + *Parameters:* * `proxyAddress` - the address of the proxy to change. From 9670db332e1c5bc146ea1009c74c205ea1b1dcc1 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Mon, 25 Sep 2023 17:10:51 -0400 Subject: [PATCH 08/14] Update note --- docs/modules/ROOT/pages/api-hardhat-upgrades.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc b/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc index 04c128c1d..a1b8b6e70 100644 --- a/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc +++ b/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc @@ -616,7 +616,7 @@ async function changeProxyAdmin( Changes the admin for a specific proxy. -NOTE: This function is not supported with transparent proxies from OpenZeppelin Contracts 5.x, since those use immutable values for their admin address. +NOTE: This function is not supported with admins or proxies from OpenZeppelin Contracts 5.x. *Parameters:* From b3e436f104b703ac1ecd42edb96a1ed111a036b3 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Mon, 25 Sep 2023 17:20:43 -0400 Subject: [PATCH 09/14] Update changelog --- packages/plugin-hardhat/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/plugin-hardhat/CHANGELOG.md b/packages/plugin-hardhat/CHANGELOG.md index bbb7d8944..8c2feb1f4 100644 --- a/packages/plugin-hardhat/CHANGELOG.md +++ b/packages/plugin-hardhat/CHANGELOG.md @@ -2,7 +2,8 @@ ## Unreleased -- Enable upgrading UUPS implementations depending on upgrade interface version. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) +- Enable upgrading UUPS implementations from OpenZeppelin Contracts 5.0, and importing and upgrading 5.0 proxies. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) + - **Note**: Deploying 5.0 proxies is not supported yet. ## 2.2.1 (2023-08-18) From 980ff69dc823ec93f178889ea38dacdc34709a84 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Tue, 26 Sep 2023 21:58:43 -0400 Subject: [PATCH 10/14] Rename to signature --- .../{call-optional-selector.ts => call-optional-signature.ts} | 4 ++-- packages/core/src/impl-address.ts | 4 ++-- packages/core/src/upgrade-interface-version.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename packages/core/src/{call-optional-selector.ts => call-optional-signature.ts} (71%) diff --git a/packages/core/src/call-optional-selector.ts b/packages/core/src/call-optional-signature.ts similarity index 71% rename from packages/core/src/call-optional-selector.ts rename to packages/core/src/call-optional-signature.ts index fb1d86af8..dc862a434 100644 --- a/packages/core/src/call-optional-selector.ts +++ b/packages/core/src/call-optional-signature.ts @@ -1,8 +1,8 @@ import { keccak256 } from 'ethereumjs-util'; import { call, EthereumProvider } from './provider'; -export async function callOptionalSelector(provider: EthereumProvider, address: string, selector: string) { - const data = '0x' + keccak256(Buffer.from(selector)).toString('hex').slice(0, 8); +export async function callOptionalSignature(provider: EthereumProvider, address: string, signature: string) { + const data = '0x' + keccak256(Buffer.from(signature)).toString('hex').slice(0, 8); try { return await call(provider, address, data); } catch (e: any) { diff --git a/packages/core/src/impl-address.ts b/packages/core/src/impl-address.ts index 6bc462543..8c2b2291f 100644 --- a/packages/core/src/impl-address.ts +++ b/packages/core/src/impl-address.ts @@ -5,7 +5,7 @@ import { getImplementationAddress, UpgradesError, } from '.'; -import { callOptionalSelector } from './call-optional-selector'; +import { callOptionalSignature } from './call-optional-signature'; import { EthereumProvider } from './provider'; import { parseAddress } from './utils/address'; @@ -23,7 +23,7 @@ export async function getImplementationAddressFromBeacon( provider: EthereumProvider, beaconAddress: string, ): Promise { - const impl = await callOptionalSelector(provider, beaconAddress, 'implementation()'); + const impl = await callOptionalSignature(provider, beaconAddress, 'implementation()'); let parsedImplAddress; if (impl !== undefined) { parsedImplAddress = parseAddress(impl); diff --git a/packages/core/src/upgrade-interface-version.ts b/packages/core/src/upgrade-interface-version.ts index c06392375..8667c397a 100644 --- a/packages/core/src/upgrade-interface-version.ts +++ b/packages/core/src/upgrade-interface-version.ts @@ -1,11 +1,11 @@ -import { callOptionalSelector } from './call-optional-selector'; +import { callOptionalSignature } from './call-optional-signature'; import { EthereumProvider } from './provider'; export async function getUpgradeInterfaceVersion( provider: EthereumProvider, address: string, ): Promise { - const encodedVersion = await callOptionalSelector(provider, address, 'UPGRADE_INTERFACE_VERSION()'); + const encodedVersion = await callOptionalSignature(provider, address, 'UPGRADE_INTERFACE_VERSION()'); if (encodedVersion !== undefined) { // Encoded string const buf = Buffer.from(encodedVersion.replace(/^0x/, ''), 'hex'); From 5f112bbff0f2cbeb4e5632ae07a7f8b497860f98 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Tue, 26 Sep 2023 22:10:43 -0400 Subject: [PATCH 11/14] Update packages/core/CHANGELOG.md Co-authored-by: Francisco --- packages/core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 71a3aecc9..124a0f05a 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- Support getting `UPGRADE_INTERFACE_VERSION`, fix inferring of UUPS proxies with `upgradeToAndCall`. ([883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) +- Support new upgrade interface in OpenZeppelin Contracts 5.0. ([883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) ## 1.29.0 (2023-09-19) From 69ce9a1d61919eb583b3be6499fe2aa49cef58c7 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Tue, 26 Sep 2023 22:56:41 -0400 Subject: [PATCH 12/14] Improve changelog for hardhat --- packages/plugin-hardhat/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/plugin-hardhat/CHANGELOG.md b/packages/plugin-hardhat/CHANGELOG.md index 8c2feb1f4..18815a56d 100644 --- a/packages/plugin-hardhat/CHANGELOG.md +++ b/packages/plugin-hardhat/CHANGELOG.md @@ -2,7 +2,8 @@ ## Unreleased -- Enable upgrading UUPS implementations from OpenZeppelin Contracts 5.0, and importing and upgrading 5.0 proxies. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) +- Support new upgrade interface in OpenZeppelin Contracts 5.0. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) +- Support importing and upgrading 5.0 proxies. - **Note**: Deploying 5.0 proxies is not supported yet. ## 2.2.1 (2023-08-18) From 970399f9f398c335ca196b2a371c634de54ad88f Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Wed, 27 Sep 2023 08:40:51 -0400 Subject: [PATCH 13/14] Update CHANGELOG.md --- packages/core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 72fd08553..887a2205e 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,8 +3,8 @@ ## Unreleased - Support new upgrade interface in OpenZeppelin Contracts 5.0. ([883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883)) -- Deprecate low-level API. Use [CLI or high-level API](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core) instead. - Add validations for namespaced storage layout. ([#876](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/876)) +- Deprecate low-level API. Use [CLI or high-level API](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core) instead. ## 1.29.0 (2023-09-19) From 514229431b2d05eb65db41abea2923637e0afc56 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Wed, 27 Sep 2023 10:34:27 -0400 Subject: [PATCH 14/14] Update packages/core/src/call-optional-signature.ts Co-authored-by: Hadrien Croubois --- packages/core/src/call-optional-signature.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/core/src/call-optional-signature.ts b/packages/core/src/call-optional-signature.ts index dc862a434..04121cb62 100644 --- a/packages/core/src/call-optional-signature.ts +++ b/packages/core/src/call-optional-signature.ts @@ -7,16 +7,14 @@ export async function callOptionalSignature(provider: EthereumProvider, address: return await call(provider, address, data); } catch (e: any) { if ( - !( - e.message.includes('function selector was not recognized') || - e.message.includes('invalid opcode') || - e.message.includes('revert') || - e.message.includes('execution error') - ) + e.message.includes('function selector was not recognized') || + e.message.includes('invalid opcode') || + e.message.includes('revert') || + e.message.includes('execution error') ) { - throw e; - } else { return undefined; + } else { + throw e; } } }