diff --git a/README.md b/README.md index 313141a80..972cec753 100644 --- a/README.md +++ b/README.md @@ -84,17 +84,15 @@ it('works before and after upgrading', async function () { ## How do the plugins work? -Both plugins provide functions which take care of managing upgradeable deployments of your contracts. +The plugins provide functions which take care of managing upgradeable deployments of your contracts. For example, `deployProxy` does the following: 1. Validate that the implementation is [upgrade safe](https://docs.openzeppelin.com/upgrades-plugins/faq#what-does-it-mean-for-a-contract-to-be-upgrade-safe) -2. Deploy a [proxy admin](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-a-proxy-admin) for your project (if needed) - -3. Check if there is an [implementation contract](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-an-implementation-contract) deployed with the same bytecode, and deploy one if not +2. Check if there is an [implementation contract](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-an-implementation-contract) deployed with the same bytecode, and deploy one if not -4. Create and initialize the proxy contract +3. Create and initialize the proxy contract, along with a [proxy admin](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-a-proxy-admin) (if needed) And when you call `upgradeProxy`: @@ -104,7 +102,7 @@ And when you call `upgradeProxy`: 3. Upgrade the proxy to use the new implementation contract -The plugins will keep track of all the implementation contracts you have deployed in an `.openzeppelin` folder in the project root, as well as the proxy admin. You will find one file per network there. It is advised that you commit to source control the files for all networks except the development ones (you may see them as `.openzeppelin/unknown-*.json`). +The plugins will keep track of all the implementation contracts you have deployed in an `.openzeppelin` folder in the project root. You will find one file per network there. It is advised that you commit to source control the files for all networks except the development ones (you may see them as `.openzeppelin/unknown-*.json`). > Note: the format of the files within the `.openzeppelin` folder is not compatible with those of the [OpenZeppelin CLI](https://docs.openzeppelin.com/cli). If you want to use these plugins for an existing OpenZeppelin CLI project, we will be sharing soon a guide on how to migrate. @@ -116,9 +114,9 @@ For UUPS and transparent proxies, use `deployProxy` and `upgradeProxy` as shown ## Managing ownership -Transparent proxies define an _admin_ address which has the rights to upgrade them. By default, the admin is a [proxy admin contract](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-a-proxy-admin) deployed behind the scenes. You can change the admin of a proxy by calling the `admin.changeProxyAdmin` function in the plugin. Keep in mind that the _admin_ of a proxy can only upgrade it, but not interact with the implementation contract. Read [here](https://docs.openzeppelin.com/upgrades-plugins/proxies#transparent-proxies-and-function-clashes) for more info on this restriction. +Transparent proxies have an _admin_ address which has the rights to upgrade them. By default, the admin is a [proxy admin contract](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-a-proxy-admin) deployed behind the scenes. Keep in mind that the _admin_ of a proxy can only upgrade it, but not interact with the implementation contract. Read [here](https://docs.openzeppelin.com/upgrades-plugins/proxies#transparent-proxies-and-function-clashes) for more info on this restriction. -The proxy admin contract also defines an _owner_ address which has the rights to operate it. By default, this address is the externally owned account used during deployment. You can change the proxy admin owner by calling the `admin.transferProxyAdminOwnership` function in the plugin. Note that changing the proxy admin owner effectively transfers the power to upgrade any proxy in your whole project to the new owner, so use with care. Refer to each plugin documentation for more details on the `admin` functions. +The proxy admin contract also defines an _owner_ address which has the rights to operate it. By default, this address is the `initialOwner` address used during deployment of the transparent proxy if provided, otherwise it is the externally owned account used during deployment. You can change the proxy admin owner by calling the `admin.transferProxyAdminOwnership` function in the plugin. Refer to each plugin documentation for more details on the `admin` functions. UUPS and beacon proxies do not use admin addresses. UUPS proxies rely on an [`_authorizeUpgrade`](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable-_authorizeUpgrade-address-) function to be overridden to include access restriction to the upgrade mechanism, whereas beacon proxies are upgradable only by the owner of their corresponding beacon. diff --git a/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc b/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc index a1b8b6e70..600762af4 100644 --- a/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc +++ b/docs/modules/ROOT/pages/api-hardhat-upgrades.adoc @@ -19,8 +19,10 @@ The following options are common to some functions. * `unsafeAllowRenames`: (`boolean`) Configure storage layout check to allow variable renaming. * `unsafeSkipStorageCheck`: (`boolean`) upgrades the proxy or beacon without first checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort. * `constructorArgs`: (`unknown[]`) Provide arguments for the constructor of the implementation contract. Note that these are different from initializer arguments, and will be used in the deployment of the implementation contract itself. Can be used to initialize immutable variables. -* `timeout`: (`number`) Timeout in milliseconds to wait for the transaction confirmation when deploying an implementation contract or proxy admin contract. Defaults to `60000`. Use `0` to wait indefinitely. -* `pollingInterval`: (`number`) Polling interval in milliseconds between checks for the transaction confirmation when deploying an implementation contract or proxy admin contract. Defaults to `5000`. +* `initialOwner`: (`string`) the address to set as the initial owner of a transparent proxy's admin or initial owner of a beacon. Defaults to the externally owned account that is deploying the transparent proxy or beacon. Not supported for UUPS proxies. +** *Since:* `@openzeppelin/hardhat-upgrades@3.0.0` +* `timeout`: (`number`) Timeout in milliseconds to wait for the transaction confirmation when deploying an implementation contract. Defaults to `60000`. Use `0` to wait indefinitely. +* `pollingInterval`: (`number`) Polling interval in milliseconds between checks for the transaction confirmation when deploying an implementation contract. Defaults to `5000`. * `redeployImplementation`: (`"always" | "never" | "onchange"`) Determines whether the implementation contract will be redeployed. Defaults to `"onchange"`. ** If set to `"always"`, the implementation contract is always redeployed even if it was previously deployed with the same bytecode. This can be used with the `salt` option when deploying a proxy through OpenZeppelin Defender to ensure that the implementation contract is deployed with the same salt as the proxy. ** If set to `"never"`, the implementation contract is never redeployed. If the implementation contract was not previously deployed or is not found in the network file, an error will be thrown. @@ -51,6 +53,7 @@ async function deployProxy( initializer?: string | false, unsafeAllow?: ValidationError[], constructorArgs?: unknown[], + initialOwner?: string, timeout?: number, pollingInterval?: number, redeployImplementation?: 'always' | 'never' | 'onchange', @@ -124,6 +127,7 @@ async function deployBeacon( opts?: { unsafeAllow?: ValidationError[], constructorArgs?: unknown[], + initialOwner?: string, timeout?: number, pollingInterval?: number, redeployImplementation?: 'always' | 'never' | 'onchange', @@ -568,37 +572,6 @@ Similar to `prepareUpgrade`. This method validates and deploys the new implement * an object with the URL of the Defender proposal, and the ethers transaction response corresponding to the deployment of the new implementation contract. Note that if the new implementation contract was originally imported as a result of `forceImport`, the ethers transaction response will be undefined. -[[deploy-proxy-admin]] -== deployProxyAdmin - -[source,ts] ----- -async function deployProxyAdmin( - signer?: ethers.Signer, - opts?: { - timeout?: number, - pollingInterval?: number, - txOverrides?: ethers.Overrides, - }, -): Promise ----- - -Deploys a https://docs.openzeppelin.com/contracts/4.x/api/proxy#ProxyAdmin[proxy admin] contract and returns its address if one was not already deployed on the current network, or just returns the address of the proxy admin if one was already deployed. Note that this plugin currently only supports using one proxy admin per network. - -*Parameters:* - -* `signer` - the signer to use for deployment. -* `opts` - an object with options: -** additional options as described in <>. - -*Returns:* - -* the address of the proxy admin. - -*Since:* - -* `@openzeppelin/hardhat-upgrades@1.20.0` - [[admin-change-proxy-admin]] == admin.changeProxyAdmin @@ -632,7 +605,8 @@ NOTE: This function is not supported with admins or proxies from OpenZeppelin Co [source,ts] ---- async function transferProxyAdminOwnership( - newAdmin: string, + proxyAddress: string, + newOwner: string, signer?: ethers.Signer, opts?: { txOverrides?: ethers.Overrides, @@ -640,15 +614,22 @@ async function transferProxyAdminOwnership( ): Promise ---- -Changes the owner of the proxy admin contract, which is the default admin for upgrade rights over all proxies. +Changes the owner of the proxy admin contract for a specific proxy. + +NOTE: The `proxyAddress` parameter is required since `@openzeppelin/hardhat-upgrades@3.0.0` *Parameters:* -* `newAdmin` - the new admin address. +* `proxyAddress` - the address of the proxy whose admin ownership is to be transferred. +* `newOwner` - the new owner address for the proxy admin contract. * `signer` - the signer to use for the transaction. * `opts` - an object with options: ** additional options as described in <>. +*Since:* + +* `@openzeppelin/hardhat-upgrades@3.0.0` + [[erc1967]] == erc1967 @@ -712,9 +693,9 @@ The arguments are the same as for hardhat-verify's `verify` task. If the provid The following contracts will be verified when you run this task on your proxy address: * Your implementation contract -* https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy[ERC1967Proxy] or https://docs.openzeppelin.com/contracts/4.x/api/proxy#TransparentUpgradeableProxy[TransparentUpgradeableProxy] or https://docs.openzeppelin.com/contracts/4.x/api/proxy#BeaconProxy[BeaconProxy] (for UUPS, transparent, or beacon proxies, respectively) -* https://docs.openzeppelin.com/contracts/4.x/api/proxy#ProxyAdmin[ProxyAdmin] (with transparent proxies) -* https://docs.openzeppelin.com/contracts/4.x/api/proxy#UpgradeableBeacon[UpgradeableBeacon] (with beacon proxies) +* https://docs.openzeppelin.com/contracts/api/proxy#ERC1967Proxy[ERC1967Proxy] or https://docs.openzeppelin.com/contracts/api/proxy#TransparentUpgradeableProxy[TransparentUpgradeableProxy] or https://docs.openzeppelin.com/contracts/api/proxy#BeaconProxy[BeaconProxy] (for UUPS, transparent, or beacon proxies, respectively) +* https://docs.openzeppelin.com/contracts/api/proxy#ProxyAdmin[ProxyAdmin] (with transparent proxies) +* https://docs.openzeppelin.com/contracts/api/proxy#UpgradeableBeacon[UpgradeableBeacon] (with beacon proxies) *Since:* diff --git a/docs/modules/ROOT/pages/faq.adoc b/docs/modules/ROOT/pages/faq.adoc index 061b21e1f..9c100caa9 100644 --- a/docs/modules/ROOT/pages/faq.adoc +++ b/docs/modules/ROOT/pages/faq.adoc @@ -147,7 +147,7 @@ You can read more about how to make storage-compatible changes to an implementat [[what-is-a-proxy-admin]] == What is a proxy admin? -A `ProxyAdmin` is a contract that acts as the owner of all your proxies. Only one per network gets deployed. When you start your project, the `ProxyAdmin` is owned by the deployer address, but you can transfer ownership of it by calling xref:contracts:api:access.adoc#Ownable-transferOwnership-address-[`transferOwnership`]. +A `ProxyAdmin` is an intermediary contract that acts as the upgrader of a transparent proxy. Each `ProxyAdmin` is owned by the deployer address, or by the `initialOwner` address when deploying a transparent proxy from OpenZeppelin Contracts 5.0 or above. You can transfer the ownership of a proxy admin by calling xref:contracts:api:access.adoc#Ownable-transferOwnership-address-[`transferOwnership`]. [[what-is-an-implementation-contract]] == What is an implementation contract? diff --git a/docs/modules/ROOT/pages/hardhat-upgrades.adoc b/docs/modules/ROOT/pages/hardhat-upgrades.adoc index a1c0e97ab..eeab1ef82 100644 --- a/docs/modules/ROOT/pages/hardhat-upgrades.adoc +++ b/docs/modules/ROOT/pages/hardhat-upgrades.adoc @@ -44,7 +44,7 @@ async function main() { main(); ---- -This will automatically check that the `Box` contract is upgrade-safe, set up a proxy admin (if needed), deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy, and initialize it by calling `initialize(42)`. +This will automatically check that the `Box` contract is upgrade-safe, deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy (along with a proxy admin if needed), and initialize it by calling `initialize(42)`. Then, in another script, you can use the `upgradeProxy` function to upgrade the deployed instance to a new version. The new version can be a different contract (such as `BoxV2`), or you can just modify the existing `Box` contract and recompile it - the plugin will note it changed. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 42e569667..19a055d1b 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -112,17 +112,15 @@ it('works before and after upgrading', async function () { [[how-plugins-work]] == How the plugins work -Both plugins provide functions which take care of managing upgradeable deployments of your contracts. +The plugins provide functions which take care of managing upgradeable deployments of your contracts. For example, `deployProxy` does the following: 1. Validate that the implementation is xref:faq.adoc#what-does-it-mean-for-a-contract-to-be-upgrade-safe[upgrade safe]. -2. Deploy a xref:faq.adoc#what-is-a-proxy-admin[proxy admin] for your project (if needed). +2. Deploy the xref:faq.adoc#what-is-an-implementation-contract[implementation contract]. -3. Deploy the xref:faq.adoc#what-is-an-implementation-contract[implementation contract]. - -4. Create and initialize the proxy contract. +3. Create and initialize the proxy contract, along with a xref:faq.adoc#what-is-a-proxy-admin[proxy admin] (if needed). And when you call `upgradeProxy`: diff --git a/docs/modules/ROOT/pages/network-files.adoc b/docs/modules/ROOT/pages/network-files.adoc index 57eae1942..8a83d9988 100644 --- a/docs/modules/ROOT/pages/network-files.adoc +++ b/docs/modules/ROOT/pages/network-files.adoc @@ -1,6 +1,6 @@ = Network Files -OpenZeppelin Upgrades keep track of all the contract versions you have deployed in an `.openzeppelin` folder in the project root, as well as the proxy admin. You will find one file per network there. It is advised that you commit to source control the files for all networks except the development ones (you may see them as `.openzeppelin/unknown-*.json`). +OpenZeppelin Upgrades keep track of all the contract versions you have deployed in an `.openzeppelin` folder in the project root. You will find one file per network there. It is advised that you commit to source control the files for all networks except the development ones (you may see them as `.openzeppelin/unknown-*.json`). NOTE: The format of the files within the `.openzeppelin` folder is not compatible with those of the xref:cli::index.adoc[OpenZeppelin CLI]. If you want to use these plugins for an existing OpenZeppelin CLI project, you have to migrate it first. See xref:migrate-from-cli.adoc[Migrate from CLI] for instructions. @@ -32,10 +32,6 @@ OpenZeppelin Upgrades will generate a file for each of the networks you work on "types": {...} } } - }, - "admin": { - "address": "...", - "txHash": "..." } } ---- diff --git a/docs/modules/ROOT/pages/proxies.adoc b/docs/modules/ROOT/pages/proxies.adoc index c8bec77f7..033357212 100644 --- a/docs/modules/ROOT/pages/proxies.adoc +++ b/docs/modules/ROOT/pages/proxies.adoc @@ -185,7 +185,7 @@ Assuming a proxy with an `owner()` and an `upgradeTo()` function, that delegates |Other |returns erc20.owner() |fails |returns erc20.transfer() |============================================================== -Fortunately, OpenZeppelin Upgrades accounts for this situation, and creates an intermediary ProxyAdmin contract that is in charge of all the proxies you create via the Upgrades plugins. Even if you call the `deploy` command from your node's default account, the ProxyAdmin contract will be the actual admin of all your proxies. This means that you will be able to interact with the proxies from any of your node's accounts, without having to worry about the nuances of the transparent proxy pattern. Only advanced users that create proxies from Solidity need to be aware of the transparent proxies pattern. +Fortunately, OpenZeppelin Upgrades accounts for this situation, and uses an intermediary ProxyAdmin contract for each transparent proxy. Even if you call the `deploy` command from your node's default account, the ProxyAdmin contracts will be the actual admins of your transparent proxies. This means that you will be able to interact with the proxies from any of your node's accounts, without having to worry about the nuances of the transparent proxy pattern. Only advanced users that create proxies from Solidity need to be aware of the transparent proxies pattern. [[summary]] == Summary diff --git a/package.json b/package.json index f22d7d10c..18c60e8df 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "scripts": { "docs": "oz-docs", "docs:watch": "oz-docs watch", - "prepare": "wsrun -ms compile:contracts && tsc -b", + "prepare": "wsrun -ms prepare && tsc -b", "lint": "yarn lint:path .", "lint:path": "eslint --ignore-path .gitignore --max-warnings 0", - "test": "tsc -b && wsrun -ms test", + "test": "wsrun -ms test", "coverage": "nyc yarn test" }, "devDependencies": { @@ -19,7 +19,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-unicorn": "^49.0.0", - "ethers": "^6.6.0", + "ethers": "^6.8.1", "lerna": "^5.0.0", "nyc": "^15.1.0", "prettier": "^3.0.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 56aace436..d3643b3cc 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Support deploying proxies from OpenZeppelin Contracts 5.0. + ## 1.31.3 (2023-11-28) - Fix Hardhat compile errors when contracts have overloaded functions or standalone NatSpec documentation. ([#918](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/918)) diff --git a/packages/core/contracts/import.sol b/packages/core/contracts/import.sol index a329380cd..120d23001 100644 --- a/packages/core/contracts/import.sol +++ b/packages/core/contracts/import.sol @@ -1,13 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; - -// Kept for backwards compatibility with older versions of Hardhat and Truffle plugins. -contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy { - constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {} -} diff --git a/packages/core/contracts/test/Proxiable.sol b/packages/core/contracts/test/Proxiable.sol index 88eda51ef..03e370449 100644 --- a/packages/core/contracts/test/Proxiable.sol +++ b/packages/core/contracts/test/Proxiable.sol @@ -1,16 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.2; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +// These contracts are for testing only, they are not safe for use in production. -abstract contract Proxiable is UUPSUpgradeable { - function _authorizeUpgrade(address newImplementation) internal override { +abstract contract BrokenProxiable { + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + + // NOT SAFE FOR PRODUCTION USE. + // This does NOT actually perform any upgrade, but is only for tests to check that this function exists. + function upgradeToAndCall(address newImplementation, bytes calldata data) external { + _authorizeUpgrade(newImplementation); + } + + function _authorizeUpgrade(address newImplementation) internal { _beforeUpgrade(newImplementation); } function _beforeUpgrade(address newImplementation) internal virtual; } -contract ChildOfProxiable is Proxiable { +contract ChildOfProxiable is BrokenProxiable { function _beforeUpgrade(address newImplementation) internal virtual override {} } diff --git a/packages/core/hardhat.config.js b/packages/core/hardhat.config.js index 37fccdb01..d4e12e736 100644 --- a/packages/core/hardhat.config.js +++ b/packages/core/hardhat.config.js @@ -21,13 +21,25 @@ const settings = { }, }; +const settingsWithParisEVM = { + ...settings, + evmVersion: 'paris', +}; + +const proxyCompiler = { + version: require('./src/solidity-version.json'), + settings: settingsWithParisEVM, +}; + function getNamespacedOverrides() { const contracts = fs.readdirSync(path.join(__dirname, 'contracts', 'test')); const namespacedContracts = contracts.filter(c => c.startsWith('Namespaced')); const overrides = {}; for (const c of namespacedContracts) { - if (c !== 'NamespacedToModify07.sol') { - overrides[`contracts/test/${c}`] = { version: '0.8.20', settings: { ...settings, evmVersion: 'paris' } }; + if (c === 'NamespacedToModify07.sol') { + overrides[`contracts/test/${c}`] = { version: '0.7.6', settings }; + } else { + overrides[`contracts/test/${c}`] = { version: '0.8.20', settings: settingsWithParisEVM }; } } return overrides; @@ -49,6 +61,7 @@ module.exports = { { version: '0.7.6', settings }, { version: '0.8.8', settings }, { version: '0.8.9', settings }, + proxyCompiler, ], overrides: getNamespacedOverrides(), }, diff --git a/packages/core/hardhat/separate-test-contracts.js b/packages/core/hardhat/separate-test-contracts.js index dfc0986e2..291a1c225 100644 --- a/packages/core/hardhat/separate-test-contracts.js +++ b/packages/core/hardhat/separate-test-contracts.js @@ -15,6 +15,8 @@ task(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, async (params, _, runSu // This is needed because CLI tests validate the entire build-info file, so each build-info should include only relevant contracts. if (params.file.sourceName.startsWith('contracts/test/cli/')) { mark(job, params.file.sourceName); + } else if (params.file.sourceName.startsWith('contracts/test/Namespaced')) { + mark(job, 'testNamespaced'); } else { mark(job, 'test'); } diff --git a/packages/core/package.json b/packages/core/package.json index 83f104139..2e7212fbc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/upgrades-core", - "version": "1.31.3", + "version": "1.32.0-alpha.0", "description": "", "repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/core", "license": "MIT", @@ -13,22 +13,29 @@ "/artifacts/contracts/proxy/AdminUpgradeabilityProxy.sol/AdminUpgradeabilityProxy.json", "/artifacts/contracts/proxy/ProxyAdmin.sol/ProxyAdmin.json", "/contracts/Initializable.sol", - "/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol", - "/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol", - "/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol", + "/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json", + "/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json", + "/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json", "/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json", "/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json", "/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json", - "/artifacts/build-info.json" + "/artifacts/build-info.json", + "/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json", + "/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json", + "/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json", + "/artifacts/@openzeppelin/contracts-v5/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json", + "/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json", + "/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json", + "/artifacts/build-info-v5.json" ], "scripts": { "clean": "hardhat clean && rimraf dist *.tsbuildinfo", "compile": "tsc -b && yarn compile:contracts", - "compile:contracts": "hardhat compile && node scripts/copy-build-info.js", - "prepare": "yarn clean && yarn compile", - "prepublishOnly": "bash scripts/copy-artifacts.sh", - "test": "tsc -b && hardhat compile --force && node scripts/copy-build-info.js && ava", - "test:watch": "hardhat compile --force && fgbg 'ava --watch' 'tsc -b --watch' --", + "compile:contracts": "hardhat compile", + "copyfiles": "node scripts/copy-build-info-v5.js && bash scripts/copy-legacy-artifacts.sh", + "prepare": "yarn clean && yarn compile && yarn copyfiles", + "test": "tsc -b && hardhat compile --force && yarn copyfiles && ava", + "test:watch": "hardhat compile --force && yarn copyfiles && fgbg 'ava --watch' 'tsc -b --watch' --", "version": "node ../../scripts/bump-changelog.js", "cli": "node ./dist/cli/cli.js" }, @@ -38,8 +45,7 @@ "devDependencies": { "@ava/typescript": "^4.0.0", "@nomicfoundation/hardhat-ethers": "^3.0.5", - "@openzeppelin/contracts": "4.8.3", - "@openzeppelin/contracts-upgradeable": "4.8.3", + "@openzeppelin/contracts": "5.0.0", "@types/cbor": "^5.0.0", "@types/debug": "^4.1.5", "@types/mocha": "^7.0.2", @@ -52,7 +58,8 @@ "fgbg": "^0.1.4", "hardhat": "^2.19.1", "rimraf": "^5.0.0", - "typescript": "^4.0.0" + "typescript": "^4.0.0", + "@openzeppelin/upgrades-core-legacy": "npm:@openzeppelin/upgrades-core@1.31.3" }, "dependencies": { "cbor": "^9.0.0", diff --git a/packages/core/scripts/copy-artifacts.sh b/packages/core/scripts/copy-artifacts.sh deleted file mode 100644 index a729fa3e5..000000000 --- a/packages/core/scripts/copy-artifacts.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Copies proxy artifacts to their location in previous versions for backwards compatibility - -mkdir artifacts/contracts/proxy/{,AdminUpgradeabilityProxy.sol,ProxyAdmin.sol} - -cp artifacts/contracts/import.sol/AdminUpgradeabilityProxy.json artifacts -cp artifacts/contracts/import.sol/AdminUpgradeabilityProxy.json artifacts/contracts/proxy/AdminUpgradeabilityProxy.sol - -cp artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json artifacts -cp artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json artifacts/contracts/proxy/ProxyAdmin.sol diff --git a/packages/core/scripts/copy-build-info.js b/packages/core/scripts/copy-build-info-v5.js similarity index 82% rename from packages/core/scripts/copy-build-info.js rename to packages/core/scripts/copy-build-info-v5.js index ef2ba65cb..abbb53672 100644 --- a/packages/core/scripts/copy-build-info.js +++ b/packages/core/scripts/copy-build-info-v5.js @@ -95,4 +95,19 @@ assert(hasProperty(sources, '@openzeppelin/contracts/proxy/transparent/ProxyAdmi // Assert that the build-info file does NOT contain test contracts assert(!hasPropertyStartsWith(sources, 'contracts/test')); -writeJSON('artifacts/build-info.json', modifiedBuildInfo); +// Assert that output bytecode does not contain PUSH0 +for (const contractFile in modifiedBuildInfo.output.contracts) { + const contractNames = contractFiles[contractFile]; + for (const contractName in contractNames) { + const bytecode = contractNames[contractName].evm.bytecode.opcodes; + assert(!bytecode.includes('PUSH0')); + } +} + +writeJSON('artifacts/build-info-v5.json', modifiedBuildInfo); + +// Moves v5 contracts to a separate folder (replacing it if it already exists, since this may be run after a recompilation) +if (fs.existsSync('artifacts/@openzeppelin/contracts-v5')) { + fs.rmSync('artifacts/@openzeppelin/contracts-v5', { recursive: true }); +} +fs.renameSync('artifacts/@openzeppelin/contracts', 'artifacts/@openzeppelin/contracts-v5'); diff --git a/packages/core/scripts/copy-legacy-artifacts.sh b/packages/core/scripts/copy-legacy-artifacts.sh new file mode 100644 index 000000000..813697c82 --- /dev/null +++ b/packages/core/scripts/copy-legacy-artifacts.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Copies proxy artifacts to their location in previous versions for backwards compatibility + +mkdir -p artifacts + +# Assert that a previous version of @openzeppelin/upgrades-core is installed, which contains legacy artifacts. +if ! jq -r .version ../../node_modules/@openzeppelin/upgrades-core-legacy/package.json | grep -q '^1.31.3$'; then + echo "Error: @openzeppelin/upgrades-core must depend on a previous version of itself at version 1.31.3" + exit 1 +fi + +# Assert that each of the legacy artifacts exists in the previous version +for file in \ + artifacts/AdminUpgradeabilityProxy.json \ + artifacts/ProxyAdmin.json \ + artifacts/contracts/proxy/AdminUpgradeabilityProxy.sol/AdminUpgradeabilityProxy.json \ + artifacts/contracts/proxy/ProxyAdmin.sol/ProxyAdmin.json \ + artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json \ + artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json \ + artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json \ + artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json \ + artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json \ + artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json \ + artifacts/build-info.json +do + if [ ! -f "../../node_modules/@openzeppelin/upgrades-core-legacy/$file" ]; then + echo "Error: @openzeppelin/upgrades-core must depend on a previous version of itself which contains $file" + exit 1 + fi +done + +cp -R ../../node_modules/@openzeppelin/upgrades-core-legacy/artifacts . diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f66e08efd..9e9ea2cfd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -53,6 +53,7 @@ export { NoContractImportError, ValidateUpdateRequiresKindError, PrepareUpgradeRequiresKindError, + InitialOwnerUnsupportedKindError, assertNotProxy, } from './usage-error'; diff --git a/packages/core/src/solidity-version.json b/packages/core/src/solidity-version.json new file mode 100644 index 000000000..aff649c04 --- /dev/null +++ b/packages/core/src/solidity-version.json @@ -0,0 +1 @@ +"0.8.20" \ No newline at end of file diff --git a/packages/core/src/standalone.test.ts b/packages/core/src/standalone.test.ts index 9a99dec43..89c55455c 100644 --- a/packages/core/src/standalone.test.ts +++ b/packages/core/src/standalone.test.ts @@ -1,6 +1,8 @@ import _test, { TestFn } from 'ava'; import { artifacts } from 'hardhat'; +import SOLIDITY_VERSION from './solidity-version.json'; + import { SolcInput, SolcOutput } from './solc-api'; import { UpgradeableContract } from './standalone'; @@ -21,7 +23,7 @@ test.before(async t => { }); test('reports unsafe operation', t => { - const impl = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput); + const impl = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput, {}, SOLIDITY_VERSION); const report = impl.getErrorReport(); t.false(report.ok); t.true(report.errors[0].kind === 'delegatecall'); @@ -32,6 +34,8 @@ test('reports unsafe operation - fully qualified name', t => { 'contracts/test/Standalone.sol:StandaloneV1', t.context.solcInput, t.context.solcOutput, + {}, + SOLIDITY_VERSION, ); const report = impl.getErrorReport(); t.false(report.ok); @@ -39,25 +43,55 @@ test('reports unsafe operation - fully qualified name', t => { }); test('reports storage upgrade errors', t => { - const v1 = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput); + const v1 = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput, {}, SOLIDITY_VERSION); - const v2Good = new UpgradeableContract('StandaloneV2Good', t.context.solcInput, t.context.solcOutput); + const v2Good = new UpgradeableContract( + 'StandaloneV2Good', + t.context.solcInput, + t.context.solcOutput, + {}, + SOLIDITY_VERSION, + ); const goodReport = v1.getStorageUpgradeReport(v2Good); t.true(goodReport.ok); - const v2Bad = new UpgradeableContract('StandaloneV2Bad', t.context.solcInput, t.context.solcOutput); + const v2Bad = new UpgradeableContract( + 'StandaloneV2Bad', + t.context.solcInput, + t.context.solcOutput, + {}, + SOLIDITY_VERSION, + ); const badReport = v1.getStorageUpgradeReport(v2Bad); t.false(badReport.ok); }); test('dont report renamed version update', t => { - const v1 = new UpgradeableContract('StandaloneRenameV1', t.context.solcInput, t.context.solcOutput); + const v1 = new UpgradeableContract( + 'StandaloneRenameV1', + t.context.solcInput, + t.context.solcOutput, + {}, + SOLIDITY_VERSION, + ); - const v2 = new UpgradeableContract('StandaloneRenameV2', t.context.solcInput, t.context.solcOutput); + const v2 = new UpgradeableContract( + 'StandaloneRenameV2', + t.context.solcInput, + t.context.solcOutput, + {}, + SOLIDITY_VERSION, + ); const goodReport = v1.getStorageUpgradeReport(v2); t.true(goodReport.ok); - const v3 = new UpgradeableContract('StandaloneRenameV3', t.context.solcInput, t.context.solcOutput); + const v3 = new UpgradeableContract( + 'StandaloneRenameV3', + t.context.solcInput, + t.context.solcOutput, + {}, + SOLIDITY_VERSION, + ); const goodReport2 = v2.getStorageUpgradeReport(v3); t.true(goodReport2.ok); }); diff --git a/packages/core/src/usage-error.ts b/packages/core/src/usage-error.ts index cb3931da8..6051a5266 100644 --- a/packages/core/src/usage-error.ts +++ b/packages/core/src/usage-error.ts @@ -95,6 +95,15 @@ export class PrepareUpgradeRequiresKindError extends UpgradesError { } } +export class InitialOwnerUnsupportedKindError extends UpgradesError { + constructor(kind: string) { + super( + `The \`initialOwner\` option is not supported for this kind of proxy ('${kind}')`, + () => `Set the initial owner as part of your contract's initializer arguments instead.`, + ); + } +} + export async function assertNotProxy(provider: EthereumProvider, address: string) { if (await isTransparentOrUUPSProxy(provider, address)) { throw new UpgradesError( diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index e6d68d2b6..5625f8a9e 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -3,9 +3,11 @@ "compilerOptions": { "composite": true, "outDir": "dist", - "rootDir": "src" + "rootDir": "src", + "resolveJsonModule": true, }, "include": [ - "src/**/*" + "src/**/*.ts", + "src/**/*.json" ] } diff --git a/packages/plugin-hardhat/CHANGELOG.md b/packages/plugin-hardhat/CHANGELOG.md index cf33c4644..8a895e6cb 100644 --- a/packages/plugin-hardhat/CHANGELOG.md +++ b/packages/plugin-hardhat/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## Unreleased + +- Deploy proxies from OpenZeppelin Contracts 5.0. +- Support `initialOwner` option when deploying a transparent proxy or beacon. If not set, the externally owned account used during deployment will be the default owner for the transparent proxy's admin or the beacon, respectively. + +### Breaking changes +- `deployProxy`, `deployBeacon`, `deployBeaconProxy`: Deploys proxy contracts from [OpenZeppelin Contracts 5.0](https://docs.openzeppelin.com/contracts/5.x/api/proxy). +- `deployProxy`: + - Deploying a transparent proxy automatically causes a new proxy admin contract to be deployed along with the proxy. + - New transparent proxy deployments no longer use an existing proxy admin, even if one was previously recorded in the network file. + - New proxy admins are no longer recorded in the network file. +- `deployProxyAdmin`: Removed, since proxy admins are deployed automatically by transparent proxies. +- `admin.changeProxyAdmin`: Not supported with admins or proxies from OpenZeppelin Contracts 5.0. Only supported for previously deployed admins and proxies from OpenZeppelin Contracts 4.x or below. +- `admin.transferProxyAdminOwnership`: This function no longer uses the proxy admin from the network file. It now requires a `proxyAddress` argument to be passed in. + ## 2.4.3 (2023-11-28) - Bump dependency on `@openzeppelin/upgrades-core`. ([#930](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/930)) diff --git a/packages/plugin-hardhat/README.md b/packages/plugin-hardhat/README.md index 54ba98af0..baef16d6c 100644 --- a/packages/plugin-hardhat/README.md +++ b/packages/plugin-hardhat/README.md @@ -42,7 +42,7 @@ async function main() { main(); ``` -This will automatically check that the `Box` contract is upgrade-safe, set up a proxy admin (if needed), deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy, and initialize it by calling `initialize(42)`. +This will automatically check that the `Box` contract is upgrade-safe, deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy (along with a proxy admin if needed), and initialize it by calling `initialize(42)`. Then, in another script, you can use the `upgradeProxy` function to upgrade the deployed instance to a new version. The new version can be a different contract (such as `BoxV2`), or you can just modify the existing `Box` contract and recompile it - the plugin will note it changed. diff --git a/packages/plugin-hardhat/contracts/DeployContract.sol b/packages/plugin-hardhat/contracts/DeployContract.sol index 295bc47c8..521ca41f4 100644 --- a/packages/plugin-hardhat/contracts/DeployContract.sol +++ b/packages/plugin-hardhat/contracts/DeployContract.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; +pragma solidity ^0.8.20; import {Initializable as Initializable1} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {Initializable as Initializable2} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; diff --git a/packages/plugin-hardhat/contracts/Greeter.sol b/packages/plugin-hardhat/contracts/Greeter.sol index b4187abdb..0403dbbe9 100644 --- a/packages/plugin-hardhat/contracts/Greeter.sol +++ b/packages/plugin-hardhat/contracts/Greeter.sol @@ -20,3 +20,6 @@ contract Greeter { import "./utils/Proxiable.sol"; contract GreeterProxiable is Greeter, Proxiable {} + +import "./utils/Proxiable40.sol"; +contract GreeterProxiable40 is Greeter, Proxiable40 {} \ No newline at end of file diff --git a/packages/plugin-hardhat/contracts/Greeter50Proxiable.sol b/packages/plugin-hardhat/contracts/Greeter50Proxiable.sol deleted file mode 100644 index 6300f68da..000000000 --- a/packages/plugin-hardhat/contracts/Greeter50Proxiable.sol +++ /dev/null @@ -1,6 +0,0 @@ -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/Greeter50V2Proxiable.sol b/packages/plugin-hardhat/contracts/Greeter50V2Proxiable.sol deleted file mode 100644 index e713b381f..000000000 --- a/packages/plugin-hardhat/contracts/Greeter50V2Proxiable.sol +++ /dev/null @@ -1,6 +0,0 @@ -pragma solidity ^0.5.1; - -import "./GreeterV2.sol"; -import "./utils/Proxiable50.sol"; - -contract Greeter50V2Proxiable is GreeterV2, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/Greeter50V3Proxiable.sol b/packages/plugin-hardhat/contracts/Greeter50V3Proxiable.sol deleted file mode 100644 index efb648426..000000000 --- a/packages/plugin-hardhat/contracts/Greeter50V3Proxiable.sol +++ /dev/null @@ -1,6 +0,0 @@ -pragma solidity ^0.5.1; - -import "./GreeterV3.sol"; -import "./utils/Proxiable50.sol"; - -contract Greeter50V3Proxiable is GreeterV3, Proxiable50 {} diff --git a/packages/plugin-hardhat/contracts/GreeterProxiable40Fallback.sol b/packages/plugin-hardhat/contracts/GreeterProxiable40Fallback.sol index 63cfdca5c..fd095d488 100644 --- a/packages/plugin-hardhat/contracts/GreeterProxiable40Fallback.sol +++ b/packages/plugin-hardhat/contracts/GreeterProxiable40Fallback.sol @@ -1,8 +1,8 @@ pragma solidity >= 0.6.0 <0.8.0; -import { Proxiable } from "./utils/Proxiable.sol"; +import { Proxiable40 } from "./utils/Proxiable40.sol"; -contract GreeterProxiable40Fallback is Proxiable { +contract GreeterProxiable40Fallback is Proxiable40 { string greeting; function initialize(string memory _greeting) public { diff --git a/packages/plugin-hardhat/contracts/GreeterProxiable40FallbackString.sol b/packages/plugin-hardhat/contracts/GreeterProxiable40FallbackString.sol index f5cc8978f..5c02b5c2b 100644 --- a/packages/plugin-hardhat/contracts/GreeterProxiable40FallbackString.sol +++ b/packages/plugin-hardhat/contracts/GreeterProxiable40FallbackString.sol @@ -1,8 +1,8 @@ pragma solidity >= 0.6.0 <0.8.0; -import { Proxiable } from "./utils/Proxiable.sol"; +import { Proxiable40 } from "./utils/Proxiable40.sol"; -contract GreeterProxiable40FallbackString is Proxiable { +contract GreeterProxiable40FallbackString is Proxiable40 { string greeting; function initialize(string memory _greeting) public { diff --git a/packages/plugin-hardhat/contracts/GreeterTransparent40FallbackString.sol b/packages/plugin-hardhat/contracts/GreeterTransparent40FallbackString.sol index e76642f73..a759caa50 100644 --- a/packages/plugin-hardhat/contracts/GreeterTransparent40FallbackString.sol +++ b/packages/plugin-hardhat/contracts/GreeterTransparent40FallbackString.sol @@ -8,7 +8,7 @@ interface ITransparentUpgradeableProxy { } contract UnsafeAdminFallbackString { - // NOT SAFE FOR PRODUCTION USE + // NOT SAFE FOR PRODUCTION USE. ANYONE CAN UPGRADE THE PROXY THROUGH THE BELOW. function upgrade(ITransparentUpgradeableProxy proxy, address implementation) public virtual { proxy.upgradeTo(implementation); diff --git a/packages/plugin-hardhat/contracts/GreeterV2.sol b/packages/plugin-hardhat/contracts/GreeterV2.sol index 4dcd4ecdc..d84e2ad9a 100644 --- a/packages/plugin-hardhat/contracts/GreeterV2.sol +++ b/packages/plugin-hardhat/contracts/GreeterV2.sol @@ -24,3 +24,6 @@ contract GreeterV2 { import "./utils/Proxiable.sol"; contract GreeterV2Proxiable is GreeterV2, Proxiable {} + +import "./utils/Proxiable40.sol"; +contract GreeterV2Proxiable40 is GreeterV2, Proxiable40 {} \ No newline at end of file diff --git a/packages/plugin-hardhat/contracts/GreeterV3.sol b/packages/plugin-hardhat/contracts/GreeterV3.sol index b62ff8cd7..3b065cbe9 100644 --- a/packages/plugin-hardhat/contracts/GreeterV3.sol +++ b/packages/plugin-hardhat/contracts/GreeterV3.sol @@ -28,3 +28,6 @@ contract GreeterV3 { import "./utils/Proxiable.sol"; contract GreeterV3Proxiable is GreeterV3, Proxiable {} + +import "./utils/Proxiable40.sol"; +contract GreeterV3Proxiable40 is GreeterV3, Proxiable40 {} \ No newline at end of file diff --git a/packages/plugin-hardhat/contracts/Import50.sol b/packages/plugin-hardhat/contracts/Import50.sol deleted file mode 100644 index fe639347c..000000000 --- a/packages/plugin-hardhat/contracts/Import50.sol +++ /dev/null @@ -1,8 +0,0 @@ -// 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/contracts/utils/Proxiable.sol b/packages/plugin-hardhat/contracts/utils/Proxiable.sol index 7390d43af..f64267276 100644 --- a/packages/plugin-hardhat/contracts/utils/Proxiable.sol +++ b/packages/plugin-hardhat/contracts/utils/Proxiable.sol @@ -5,9 +5,7 @@ pragma solidity >= 0.4.22 <0.8.0; contract Proxiable { bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - function upgradeTo(address newImplementation) external { - _setImplementation(newImplementation); - } + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; function upgradeToAndCall(address newImplementation, bytes calldata data) external { _setImplementation(newImplementation); @@ -27,6 +25,14 @@ contract Proxiable { */ (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'); } } diff --git a/packages/plugin-hardhat/contracts/utils/Proxiable50.sol b/packages/plugin-hardhat/contracts/utils/Proxiable40.sol similarity index 86% rename from packages/plugin-hardhat/contracts/utils/Proxiable50.sol rename to packages/plugin-hardhat/contracts/utils/Proxiable40.sol index e6162df88..59aa2cf0d 100644 --- a/packages/plugin-hardhat/contracts/utils/Proxiable50.sol +++ b/packages/plugin-hardhat/contracts/utils/Proxiable40.sol @@ -2,10 +2,12 @@ pragma solidity >= 0.4.22 <0.8.0; // This contract is for testing only, it is not safe for use in production. -contract Proxiable50 { +contract Proxiable40 { bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + function upgradeTo(address newImplementation) external { + _setImplementation(newImplementation); + } function upgradeToAndCall(address newImplementation, bytes calldata data) external { _setImplementation(newImplementation); @@ -25,14 +27,6 @@ contract Proxiable50 { */ (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'); } } diff --git a/packages/plugin-hardhat/package.json b/packages/plugin-hardhat/package.json index f3d101b0e..1514d27a5 100644 --- a/packages/plugin-hardhat/package.json +++ b/packages/plugin-hardhat/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/hardhat-upgrades", - "version": "2.4.3", + "version": "3.0.0-alpha.0", "description": "", "repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/plugin-hardhat", "license": "MIT", @@ -23,9 +23,8 @@ "devDependencies": { "@nomicfoundation/hardhat-ethers": "^3.0.5", "@nomicfoundation/hardhat-verify": "^1.1.0", - "@openzeppelin/contracts": "4.8.3", - "@openzeppelin/contracts-5.0": "npm:@openzeppelin/contracts@^5.0.0-rc.0", - "@openzeppelin/contracts-upgradeable": "4.8.3", + "@openzeppelin/contracts": "5.0.0", + "@openzeppelin/contracts-upgradeable": "5.0.0", "@types/mocha": "^7.0.2", "ava": "^5.0.0", "fgbg": "^0.1.4", @@ -40,7 +39,7 @@ "@openzeppelin/defender-base-client": "^1.52.0", "@openzeppelin/defender-sdk-base-client": "^1.5.0", "@openzeppelin/defender-sdk-deploy-client": "^1.5.0", - "@openzeppelin/upgrades-core": "^1.31.2", + "@openzeppelin/upgrades-core": "^1.32.0-alpha.0", "chalk": "^4.1.0", "debug": "^4.1.1", "ethereumjs-util": "^7.1.5", diff --git a/packages/plugin-hardhat/src/admin.ts b/packages/plugin-hardhat/src/admin.ts index bb818ad6f..53fffc97c 100644 --- a/packages/plugin-hardhat/src/admin.ts +++ b/packages/plugin-hardhat/src/admin.ts @@ -2,9 +2,8 @@ import chalk from 'chalk'; import type { HardhatRuntimeEnvironment } from 'hardhat/types'; import { Manifest, getAdminAddress } from '@openzeppelin/upgrades-core'; import { Contract, Signer } from 'ethers'; -import { EthersDeployOptions, getProxyAdminFactory } from './utils'; +import { EthersDeployOptions, attachProxyAdminV4 } from './utils'; import { disableDefender } from './defender/utils'; -import { attach } from './utils/ethers'; const SUCCESS_CHECK = chalk.green('✔') + ' '; const FAILURE_CROSS = chalk.red('✘') + ' '; @@ -16,6 +15,7 @@ export type ChangeAdminFunction = ( opts?: EthersDeployOptions, ) => Promise; export type TransferProxyAdminOwnershipFunction = ( + proxyAddress: string, newOwner: string, signer?: Signer, opts?: EthersDeployOptions, @@ -32,14 +32,11 @@ export function makeChangeProxyAdmin(hre: HardhatRuntimeEnvironment, defenderMod disableDefender(hre, defenderModule, {}, changeProxyAdmin.name); const proxyAdminAddress = await getAdminAddress(hre.network.provider, proxyAddress); + // Only compatible with v4 admins + const admin = await attachProxyAdminV4(hre, proxyAdminAddress, signer); - if (proxyAdminAddress !== newAdmin) { - const AdminFactory = await getProxyAdminFactory(hre, signer); - const admin = attach(AdminFactory, proxyAdminAddress); - - const overrides = opts.txOverrides ? [opts.txOverrides] : []; - await admin.changeProxyAdmin(proxyAddress, newAdmin, ...overrides); - } + const overrides = opts.txOverrides ? [opts.txOverrides] : []; + await admin.changeProxyAdmin(proxyAddress, newAdmin, ...overrides); }; } @@ -47,10 +44,17 @@ export function makeTransferProxyAdminOwnership( hre: HardhatRuntimeEnvironment, defenderModule: boolean, ): TransferProxyAdminOwnershipFunction { - return async function transferProxyAdminOwnership(newOwner: string, signer?: Signer, opts: EthersDeployOptions = {}) { + return async function transferProxyAdminOwnership( + proxyAddress: string, + newOwner: string, + signer?: Signer, + opts: EthersDeployOptions = {}, + ) { disableDefender(hre, defenderModule, {}, transferProxyAdminOwnership.name); - const admin = await getManifestAdmin(hre, signer); + const proxyAdminAddress = await getAdminAddress(hre.network.provider, proxyAddress); + // Compatible with both v4 and v5 admins since they both have transferOwnership + const admin = await attachProxyAdminV4(hre, proxyAdminAddress, signer); const overrides = opts.txOverrides ? [opts.txOverrides] : []; await admin.transferOwnership(newOwner, ...overrides); @@ -60,29 +64,10 @@ export function makeTransferProxyAdminOwnership( const { proxies } = await manifest.read(); for (const { address, kind } of proxies) { if ((await admin.getAddress()) == (await getAdminAddress(provider, address))) { - console.log(SUCCESS_CHECK + `${address} (${kind}) proxy ownership transfered through admin proxy`); + console.log(SUCCESS_CHECK + `${address} (${kind}) proxy ownership transferred through proxy admin`); } else { - console.log(FAILURE_CROSS + `${address} (${kind}) proxy ownership not affected by admin proxy`); + console.log(FAILURE_CROSS + `${address} (${kind}) proxy ownership not affected by proxy admin`); } } }; } - -export function makeGetInstanceFunction(hre: HardhatRuntimeEnvironment): GetInstanceFunction { - return async function getInstance(signer?: Signer) { - return await getManifestAdmin(hre, signer); - }; -} - -export async function getManifestAdmin(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise { - const manifest = await Manifest.forNetwork(hre.network.provider); - const manifestAdmin = await manifest.getAdmin(); - const proxyAdminAddress = manifestAdmin?.address; - - if (proxyAdminAddress === undefined) { - throw new Error('No ProxyAdmin was found in the network manifest'); - } - - const AdminFactory = await getProxyAdminFactory(hre, signer); - return attach(AdminFactory, proxyAdminAddress); -} diff --git a/packages/plugin-hardhat/src/defender/deploy.ts b/packages/plugin-hardhat/src/defender/deploy.ts index 1687374d9..c2a11e95b 100644 --- a/packages/plugin-hardhat/src/defender/deploy.ts +++ b/packages/plugin-hardhat/src/defender/deploy.ts @@ -11,13 +11,12 @@ import { UpgradesError, } from '@openzeppelin/upgrades-core'; -import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info.json'; +import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info-v5.json'; -import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; -import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; -import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; -import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; -import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; +import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; +import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; +import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; +import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; import { getNetwork, getDeployClient } from './utils'; import { DeployTransaction, DefenderDeployOptions, UpgradeOptions } from '../utils'; @@ -25,13 +24,7 @@ import debug from '../utils/debug'; import { getDeployData } from '../utils/deploy-impl'; import { ContractSourceNotFoundError } from '@openzeppelin/upgrades-core'; -const deployableProxyContracts = [ - ERC1967Proxy, - BeaconProxy, - UpgradeableBeacon, - TransparentUpgradeableProxy, - ProxyAdmin, -]; +const deployableProxyContracts = [ERC1967Proxy, BeaconProxy, UpgradeableBeacon, TransparentUpgradeableProxy]; interface ReducedBuildInfo { _format: string; diff --git a/packages/plugin-hardhat/src/deploy-beacon.ts b/packages/plugin-hardhat/src/deploy-beacon.ts index 1b8e4bc7b..be0b3270a 100644 --- a/packages/plugin-hardhat/src/deploy-beacon.ts +++ b/packages/plugin-hardhat/src/deploy-beacon.ts @@ -6,6 +6,7 @@ import { Deployment } from '@openzeppelin/upgrades-core'; import { DeployBeaconOptions, deploy, DeployTransaction, getUpgradeableBeaconFactory, deployBeaconImpl } from './utils'; import { disableDefender } from './defender/utils'; import { attach, getSigner } from './utils/ethers'; +import { getInitialOwner } from './utils/initial-owner'; export interface DeployBeaconFunction { (ImplFactory: ContractFactory, opts?: DeployBeaconOptions): Promise; @@ -17,8 +18,18 @@ export function makeDeployBeacon(hre: HardhatRuntimeEnvironment, defenderModule: const { impl } = await deployBeaconImpl(hre, ImplFactory, opts); - const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, getSigner(ImplFactory.runner)); - const beaconDeployment: Deployment & DeployTransaction = await deploy(hre, opts, UpgradeableBeaconFactory, impl); + const signer = getSigner(ImplFactory.runner); + const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, signer); + + const initialOwner = await getInitialOwner(opts, signer); + + const beaconDeployment: Deployment & DeployTransaction = await deploy( + hre, + opts, + UpgradeableBeaconFactory, + impl, + initialOwner, + ); const beaconContract = attach(UpgradeableBeaconFactory, beaconDeployment.address); // @ts-ignore Won't be readonly because beaconContract was created through attach. diff --git a/packages/plugin-hardhat/src/deploy-proxy-admin.ts b/packages/plugin-hardhat/src/deploy-proxy-admin.ts deleted file mode 100644 index f2f1607b2..000000000 --- a/packages/plugin-hardhat/src/deploy-proxy-admin.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { HardhatRuntimeEnvironment } from 'hardhat/types'; - -import { fetchOrDeployAdmin } from '@openzeppelin/upgrades-core'; - -import { deploy, DeployProxyAdminOptions, getProxyAdminFactory } from './utils'; -import { Signer } from 'ethers'; -import { disableDefender } from './defender/utils'; - -export interface DeployAdminFunction { - (signer?: Signer, opts?: DeployProxyAdminOptions): Promise; -} - -export function makeDeployProxyAdmin(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployAdminFunction { - return async function deployProxyAdmin(signer?: Signer, opts: DeployProxyAdminOptions = {}) { - disableDefender(hre, defenderModule, opts, deployProxyAdmin.name); - - const { provider } = hre.network; - - const AdminFactory = await getProxyAdminFactory(hre, signer); - return await fetchOrDeployAdmin(provider, () => deploy(hre, opts, AdminFactory), opts); - }; -} diff --git a/packages/plugin-hardhat/src/deploy-proxy.ts b/packages/plugin-hardhat/src/deploy-proxy.ts index a6dc7f4b0..9261827cd 100644 --- a/packages/plugin-hardhat/src/deploy-proxy.ts +++ b/packages/plugin-hardhat/src/deploy-proxy.ts @@ -7,6 +7,7 @@ import { ProxyDeployment, BeaconProxyUnsupportedError, RemoteDeploymentId, + InitialOwnerUnsupportedKindError, } from '@openzeppelin/upgrades-core'; import { @@ -21,6 +22,7 @@ import { } from './utils'; import { enableDefender } from './defender/utils'; import { getContractInstance } from './utils/contract-instance'; +import { getInitialOwner } from './utils/initial-owner'; export interface DeployFunction { (ImplFactory: ContractFactory, args?: unknown[], opts?: DeployProxyOptions): Promise; @@ -48,12 +50,17 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: const contractInterface = ImplFactory.interface; const data = getInitializerData(contractInterface, args, opts.initializer); - if (kind === 'uups') { - if (await manifest.getAdmin()) { + if (await manifest.getAdmin()) { + if (kind === 'uups') { logWarning(`A proxy admin was previously deployed on this network`, [ `This is not natively used with the current kind of proxy ('uups').`, `Changes to the admin will have no effect on this new proxy.`, ]); + } else if (kind === 'transparent') { + logWarning(`A proxy admin was previously deployed on this network`, [ + `This is not used with new transparent proxy deployments, since new transparent proxies deploy their own admins.`, + `Changes to the previous admin will have no effect on this new proxy.`, + ]); } } @@ -66,17 +73,22 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: } case 'uups': { + if (opts.initialOwner !== undefined) { + throw new InitialOwnerUnsupportedKindError(kind); + } + const ProxyFactory = await getProxyFactory(hre, signer); proxyDeployment = Object.assign({ kind }, await deploy(hre, opts, ProxyFactory, impl, data)); break; } case 'transparent': { - const adminAddress = await hre.upgrades.deployProxyAdmin(signer, opts); + const initialOwner = await getInitialOwner(opts, signer); + const TransparentUpgradeableProxyFactory = await getTransparentUpgradeableProxyFactory(hre, signer); proxyDeployment = Object.assign( { kind }, - await deploy(hre, opts, TransparentUpgradeableProxyFactory, impl, adminAddress, data), + await deploy(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data), ); break; } diff --git a/packages/plugin-hardhat/src/force-import.ts b/packages/plugin-hardhat/src/force-import.ts index bebba9aa9..879fbf58a 100644 --- a/packages/plugin-hardhat/src/force-import.ts +++ b/packages/plugin-hardhat/src/force-import.ts @@ -4,7 +4,6 @@ import type { ContractFactory, Contract } from 'ethers'; import { Manifest, getImplementationAddressFromProxy, - getAdminAddress, addProxyToManifest, isBeacon, getImplementationAddressFromBeacon, @@ -13,6 +12,7 @@ import { ProxyDeployment, hasCode, NoContractImportError, + getAdminAddress, isEmptySlot, UpgradesError, } from '@openzeppelin/upgrades-core'; @@ -24,7 +24,6 @@ import { getUpgradeableBeaconFactory, ForceImportOptions, } from './utils'; -import { simulateDeployAdmin } from './utils/simulate-deploy'; import { getDeployData } from './utils/deploy-impl'; import { attach, getSigner } from './utils/ethers'; @@ -88,8 +87,9 @@ async function importProxyToManifest( } if (importKind === 'transparent') { - await addAdminToManifest(provider, hre, proxyAddress, ImplFactory, opts); + await assertNonEmptyAdminSlot(provider, proxyAddress); } + await addProxyToManifest(importKind, proxyAddress, manifest); } @@ -102,16 +102,10 @@ async function addImplToManifest( await simulateDeployImpl(hre, ImplFactory, opts, implAddress); } -async function addAdminToManifest( - provider: EthereumProvider, - hre: HardhatRuntimeEnvironment, - proxyAddress: string, - ImplFactory: ContractFactory, - opts: ForceImportOptions, -) { +async function assertNonEmptyAdminSlot(provider: EthereumProvider, proxyAddress: string) { const adminAddress = await getAdminAddress(provider, proxyAddress); if (isEmptySlot(adminAddress)) { - // Assert that the admin slot of a transparent proxy is not zero, otherwise the simulation below would fail due to no code at the address. + // Assert that the admin slot of a transparent proxy is not zero, otherwise the wrong kind may be imported. // Note: Transparent proxies should not have the zero address as the admin, according to TransparentUpgradeableProxy's _setAdmin function. throw new UpgradesError( `Proxy at ${proxyAddress} doesn't look like a transparent proxy`, @@ -120,5 +114,4 @@ async function addAdminToManifest( `Set the \`kind\` option to the kind of proxy that was deployed at ${proxyAddress} (either 'uups' or 'beacon')`, ); } - await simulateDeployAdmin(hre, ImplFactory, opts, adminAddress); } diff --git a/packages/plugin-hardhat/src/index.ts b/packages/plugin-hardhat/src/index.ts index 836593dcb..09be3e865 100644 --- a/packages/plugin-hardhat/src/index.ts +++ b/packages/plugin-hardhat/src/index.ts @@ -14,11 +14,10 @@ import type { DeployBeaconFunction } from './deploy-beacon'; import type { DeployBeaconProxyFunction } from './deploy-beacon-proxy'; import type { UpgradeBeaconFunction } from './upgrade-beacon'; import type { ForceImportFunction } from './force-import'; -import type { ChangeAdminFunction, TransferProxyAdminOwnershipFunction, GetInstanceFunction } from './admin'; +import type { ChangeAdminFunction, TransferProxyAdminOwnershipFunction } from './admin'; import type { ValidateImplementationFunction } from './validate-implementation'; import type { ValidateUpgradeFunction } from './validate-upgrade'; import type { DeployImplementationFunction } from './deploy-implementation'; -import type { DeployAdminFunction } from './deploy-proxy-admin'; import type { DeployContractFunction } from './deploy-contract'; import type { ProposeUpgradeWithApprovalFunction } from './defender/propose-upgrade-with-approval'; import type { GetDefaultApprovalProcessFunction } from './defender/get-default-approval-process'; @@ -41,11 +40,9 @@ export interface HardhatUpgrades { deployBeacon: DeployBeaconFunction; deployBeaconProxy: DeployBeaconProxyFunction; upgradeBeacon: UpgradeBeaconFunction; - deployProxyAdmin: DeployAdminFunction; forceImport: ForceImportFunction; silenceWarnings: typeof silenceWarnings; admin: { - getInstance: GetInstanceFunction; changeProxyAdmin: ChangeAdminFunction; transferProxyAdminOwnership: TransferProxyAdminOwnershipFunction; }; @@ -217,8 +214,7 @@ function makeFunctions(hre: HardhatRuntimeEnvironment, defender: boolean) { const { makeDeployBeaconProxy } = require('./deploy-beacon-proxy'); const { makeUpgradeBeacon } = require('./upgrade-beacon'); const { makeForceImport } = require('./force-import'); - const { makeChangeProxyAdmin, makeTransferProxyAdminOwnership, makeGetInstanceFunction } = require('./admin'); - const { makeDeployProxyAdmin } = require('./deploy-proxy-admin'); + const { makeChangeProxyAdmin, makeTransferProxyAdminOwnership } = require('./admin'); return { silenceWarnings, @@ -231,10 +227,8 @@ function makeFunctions(hre: HardhatRuntimeEnvironment, defender: boolean) { deployBeacon: makeDeployBeacon(hre, defender), // block on defender deployBeaconProxy: makeDeployBeaconProxy(hre, defender), upgradeBeacon: makeUpgradeBeacon(hre, defender), // block on defender - deployProxyAdmin: makeDeployProxyAdmin(hre, defender), // block on defender forceImport: makeForceImport(hre), admin: { - getInstance: makeGetInstanceFunction(hre), changeProxyAdmin: makeChangeProxyAdmin(hre, defender), // block on defender transferProxyAdminOwnership: makeTransferProxyAdminOwnership(hre, defender), // block on defender }, diff --git a/packages/plugin-hardhat/src/upgrade-proxy.ts b/packages/plugin-hardhat/src/upgrade-proxy.ts index 97e9d4e66..0bf2acde9 100644 --- a/packages/plugin-hardhat/src/upgrade-proxy.ts +++ b/packages/plugin-hardhat/src/upgrade-proxy.ts @@ -1,20 +1,23 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import type { ethers, ContractFactory, Contract, Signer } from 'ethers'; import debug from './utils/debug'; - import { getAdminAddress, getCode, getUpgradeInterfaceVersion, isEmptySlot } from '@openzeppelin/upgrades-core'; import { UpgradeProxyOptions, deployProxyImpl, - getITransparentUpgradeableProxyFactory, - getProxyAdminFactory, getContractAddress, ContractAddressOrInstance, getSigner, } from './utils'; import { disableDefender } from './defender/utils'; import { attach } from './utils/ethers'; +import { + attachITransparentUpgradeableProxyV4, + attachITransparentUpgradeableProxyV5, + attachProxyAdminV4, + attachProxyAdminV5, +} from './utils/attach-abi'; export type UpgradeFunction = ( proxy: ContractAddressOrInstance, @@ -55,16 +58,14 @@ export function makeUpgradeProxy( const overrides = opts.txOverrides ? [opts.txOverrides] : []; if (isEmptySlot(adminAddress) || adminBytecode === '0x') { - // No admin contract: use ITransparentUpgradeableProxyFactory to get proxiable interface - const ITransparentUpgradeableProxyFactory = await getITransparentUpgradeableProxyFactory(hre, signer); - const proxy = attach(ITransparentUpgradeableProxyFactory, proxyAddress); - + // No admin contract: use ITransparentUpgradeableProxy to get proxiable interface const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, proxyAddress, log); - - return (nextImpl, call) => { - if (upgradeInterfaceVersion === '5.0.0') { - return proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides); - } else { + switch (upgradeInterfaceVersion) { + case '5.0.0': { + const proxy = await attachITransparentUpgradeableProxyV5(hre, proxyAddress, signer); + return (nextImpl, call) => proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides); + } + default: { if (upgradeInterfaceVersion !== undefined) { // Log as debug if the interface version is an unknown string. // Do not throw an error because this could be caused by a fallback function. @@ -72,20 +73,20 @@ export function makeUpgradeProxy( `Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy at ${proxyAddress}. Expected 5.0.0`, ); } - return call ? proxy.upgradeToAndCall(nextImpl, call, ...overrides) : proxy.upgradeTo(nextImpl, ...overrides); + const proxy = await attachITransparentUpgradeableProxyV4(hre, proxyAddress, signer); + return (nextImpl, call) => + call ? proxy.upgradeToAndCall(nextImpl, call, ...overrides) : proxy.upgradeTo(nextImpl, ...overrides); } - }; + } } else { // Admin contract: redirect upgrade call through it - const AdminFactory = await getProxyAdminFactory(hre, signer); - const admin = attach(AdminFactory, adminAddress); - const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, adminAddress, log); - - return (nextImpl, call) => { - if (upgradeInterfaceVersion === '5.0.0') { - return admin.upgradeAndCall(proxyAddress, nextImpl, call ?? '0x', ...overrides); - } else { + switch (upgradeInterfaceVersion) { + case '5.0.0': { + const admin = await attachProxyAdminV5(hre, adminAddress, signer); + return (nextImpl, call) => admin.upgradeAndCall(proxyAddress, nextImpl, call ?? '0x', ...overrides); + } + default: { if (upgradeInterfaceVersion !== undefined) { // Log as debug if the interface version is an unknown string. // Do not throw an error because this could be caused by a fallback function. @@ -93,11 +94,13 @@ export function makeUpgradeProxy( `Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy admin at ${adminAddress}. Expected 5.0.0`, ); } - return call - ? admin.upgradeAndCall(proxyAddress, nextImpl, call, ...overrides) - : admin.upgrade(proxyAddress, nextImpl, ...overrides); + const admin = await attachProxyAdminV4(hre, adminAddress, signer); + return (nextImpl, call) => + call + ? admin.upgradeAndCall(proxyAddress, nextImpl, call, ...overrides) + : admin.upgrade(proxyAddress, nextImpl, ...overrides); } - }; + } } } } diff --git a/packages/plugin-hardhat/src/utils/attach-abi.ts b/packages/plugin-hardhat/src/utils/attach-abi.ts new file mode 100644 index 000000000..d4a686530 --- /dev/null +++ b/packages/plugin-hardhat/src/utils/attach-abi.ts @@ -0,0 +1,41 @@ +import { Contract, Signer } from 'ethers'; + +import ITransparentUpgradeableProxyV5 from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json'; +import ITransparentUpgradeableProxyV4 from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json'; + +import ProxyAdminV5 from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; +import ProxyAdminV4 from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; + +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +export async function attachITransparentUpgradeableProxyV5( + hre: HardhatRuntimeEnvironment, + address: string, + signer?: Signer, +): Promise { + return hre.ethers.getContractAt(ITransparentUpgradeableProxyV5.abi, address, signer); +} + +export async function attachITransparentUpgradeableProxyV4( + hre: HardhatRuntimeEnvironment, + address: string, + signer?: Signer, +): Promise { + return hre.ethers.getContractAt(ITransparentUpgradeableProxyV4.abi, address, signer); +} + +export async function attachProxyAdminV5( + hre: HardhatRuntimeEnvironment, + address: string, + signer?: Signer, +): Promise { + return hre.ethers.getContractAt(ProxyAdminV5.abi, address, signer); +} + +export async function attachProxyAdminV4( + hre: HardhatRuntimeEnvironment, + address: string, + signer?: Signer, +): Promise { + return hre.ethers.getContractAt(ProxyAdminV4.abi, address, signer); +} diff --git a/packages/plugin-hardhat/src/utils/contract-instance.ts b/packages/plugin-hardhat/src/utils/contract-instance.ts index 75176ddcd..4ca1467a9 100644 --- a/packages/plugin-hardhat/src/utils/contract-instance.ts +++ b/packages/plugin-hardhat/src/utils/contract-instance.ts @@ -9,8 +9,8 @@ import { attach } from './ethers'; /** * Gets a contract instance from a deployment, where the deployment may be remote. - * If the deployment is remote, the instance have an overriden `deployed` method to wait for the remote deployment - * and update its `deployTransaction` with the new transaction hash if it was detected to have changed. + * If the deployment is remote, the instance has an overriden `waitForDeployment` method to wait for the remote deployment + * and update its `deploymentTransaction` with the new transaction hash if it was detected to have changed. * * @param hre The Hardhat Runtime Environment * @param contract The contract factory diff --git a/packages/plugin-hardhat/src/utils/factories.ts b/packages/plugin-hardhat/src/utils/factories.ts index 03a957820..ba1a07522 100644 --- a/packages/plugin-hardhat/src/utils/factories.ts +++ b/packages/plugin-hardhat/src/utils/factories.ts @@ -1,10 +1,10 @@ import { ContractFactory, Signer } from 'ethers'; -import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; -import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; -import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; -import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; -import ITransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json'; -import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; + +import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; +import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; +import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; +import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; + import { HardhatRuntimeEnvironment } from 'hardhat/types'; export async function getProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise { @@ -18,17 +18,6 @@ export async function getTransparentUpgradeableProxyFactory( return hre.ethers.getContractFactory(TransparentUpgradeableProxy.abi, TransparentUpgradeableProxy.bytecode, signer); } -export async function getITransparentUpgradeableProxyFactory( - hre: HardhatRuntimeEnvironment, - signer?: Signer, -): Promise { - return hre.ethers.getContractFactory(ITransparentUpgradeableProxy.abi, ITransparentUpgradeableProxy.bytecode, signer); -} - -export async function getProxyAdminFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise { - return hre.ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode, signer); -} - export async function getBeaconProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise { return hre.ethers.getContractFactory(BeaconProxy.abi, BeaconProxy.bytecode, signer); } diff --git a/packages/plugin-hardhat/src/utils/index.ts b/packages/plugin-hardhat/src/utils/index.ts index 6216ed56a..ff38ae3d1 100644 --- a/packages/plugin-hardhat/src/utils/index.ts +++ b/packages/plugin-hardhat/src/utils/index.ts @@ -8,3 +8,9 @@ export * from './contract-types'; export * from './options'; export * from './initializer-data'; export { attach, getSigner } from './ethers'; +export { + attachITransparentUpgradeableProxyV4, + attachITransparentUpgradeableProxyV5, + attachProxyAdminV4, + attachProxyAdminV5, +} from './attach-abi'; diff --git a/packages/plugin-hardhat/src/utils/initial-owner.ts b/packages/plugin-hardhat/src/utils/initial-owner.ts new file mode 100644 index 000000000..3b55de72b --- /dev/null +++ b/packages/plugin-hardhat/src/utils/initial-owner.ts @@ -0,0 +1,14 @@ +import { Signer } from 'ethers'; +import { InitialOwner } from './options'; +import { UpgradesError } from '@openzeppelin/upgrades-core'; + +export async function getInitialOwner(opts: InitialOwner, signer?: Signer) { + const result = opts.initialOwner ?? (await signer?.getAddress()) ?? undefined; + if (result === undefined) { + throw new UpgradesError( + 'Initial owner must be specified', + () => `Set the initial owner address using the \`initialOwner\` option`, + ); + } + return result; +} diff --git a/packages/plugin-hardhat/src/utils/options.ts b/packages/plugin-hardhat/src/utils/options.ts index 1b706f37d..db2f56c17 100644 --- a/packages/plugin-hardhat/src/utils/options.ts +++ b/packages/plugin-hardhat/src/utils/options.ts @@ -76,20 +76,23 @@ export type EthersDeployOptions = { txOverrides?: Overrides; }; +export type InitialOwner = { + initialOwner?: string; +}; + export type DeployBeaconProxyOptions = EthersDeployOptions & DeployOpts & ProxyKindOption & Initializer & DefenderDeployOptions; -export type DeployBeaconOptions = StandaloneOptions & DefenderDeploy; +export type DeployBeaconOptions = StandaloneOptions & InitialOwner & DefenderDeploy; export type DeployImplementationOptions = StandaloneOptions & GetTxResponse & DefenderDeployOptions; export type DeployContractOptions = Omit & // ethers deployment not supported for deployContract GetTxResponse & DefenderDeployOptions & { unsafeAllowDeployContract?: boolean; }; -export type DeployProxyAdminOptions = EthersDeployOptions & DeployOpts & DefenderDeploy; -export type DeployProxyOptions = StandaloneOptions & Initializer & DefenderDeployOptions; +export type DeployProxyOptions = StandaloneOptions & Initializer & InitialOwner & DefenderDeployOptions; export type ForceImportOptions = ProxyKindOption; export type PrepareUpgradeOptions = UpgradeOptions & GetTxResponse & DefenderDeployOptions; export type UpgradeBeaconOptions = UpgradeOptions & DefenderDeploy; diff --git a/packages/plugin-hardhat/src/utils/simulate-deploy.ts b/packages/plugin-hardhat/src/utils/simulate-deploy.ts index 9f2d06bd7..b8e144c60 100644 --- a/packages/plugin-hardhat/src/utils/simulate-deploy.ts +++ b/packages/plugin-hardhat/src/utils/simulate-deploy.ts @@ -1,4 +1,4 @@ -import { fetchOrDeploy, fetchOrDeployAdmin, logWarning } from '@openzeppelin/upgrades-core'; +import { fetchOrDeploy } from '@openzeppelin/upgrades-core'; import type { ContractFactory } from 'ethers'; import type { HardhatRuntimeEnvironment } from 'hardhat/types'; import { getDeployData } from './deploy-impl'; @@ -9,25 +9,6 @@ import { UpgradeOptions } from './options'; // for the "deploy" part we pass a function that simply returns the contract to be imported, rather than // actually deploying something. -export async function simulateDeployAdmin( - hre: HardhatRuntimeEnvironment, - ProxyAdminFactory: ContractFactory, - opts: UpgradeOptions, - adminAddress: string, -) { - const { deployData, simulateDeploy } = await getSimulatedData(hre, ProxyAdminFactory, opts, adminAddress); - const manifestAdminAddress = await fetchOrDeployAdmin(deployData.provider, simulateDeploy, opts); - if (adminAddress !== manifestAdminAddress) { - logWarning( - `Imported proxy with admin at '${adminAddress}' which differs from previously deployed admin '${manifestAdminAddress}'`, - [ - `The imported proxy admin is different from the proxy admin that was previously deployed on this network.`, - `New proxy deployments will continue to use the admin '${manifestAdminAddress}'.`, - ], - ); - } -} - export async function simulateDeployImpl( hre: HardhatRuntimeEnvironment, ImplFactory: ContractFactory, diff --git a/packages/plugin-hardhat/src/verify-proxy.ts b/packages/plugin-hardhat/src/verify-proxy.ts index 173126208..773572b42 100644 --- a/packages/plugin-hardhat/src/verify-proxy.ts +++ b/packages/plugin-hardhat/src/verify-proxy.ts @@ -9,16 +9,17 @@ import { isBeacon, isBeaconProxy, isEmptySlot, + getCode, } from '@openzeppelin/upgrades-core'; -import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info.json'; +import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info-v5.json'; import { HardhatRuntimeEnvironment, RunSuperFunction } from 'hardhat/types'; -import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; -import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; -import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; -import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; -import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; +import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; +import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; +import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; +import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; +import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; import { keccak256 } from 'ethereumjs-util'; @@ -35,6 +36,7 @@ interface ContractArtifact { sourceName: string; abi: any; bytecode: any; + deployedBytecode: any; } /** @@ -50,6 +52,14 @@ interface ErrorReport { severity: 'error' | 'warn'; } +/** + * Etherscan API response when getting event logs by address and topic. + */ +interface EtherscanEventResponse { + topics: string[]; + transactionHash: string; +} + /** * The proxy-related contracts and their corresponding events that may have been deployed the current version of this plugin. */ @@ -159,9 +169,21 @@ function recordError(message: string, errorReport: ErrorReport) { } /** - * Indicates that the expected event topic was not found in the contract's logs according to the Etherscan API. + * Indicates that the expected event topic was not found in the contract's logs according to the Etherscan API, or an expected function was not found. */ -class EventNotFound extends UpgradesError {} +class EventOrFunctionNotFound extends UpgradesError {} + +class EventsNotFound extends EventOrFunctionNotFound { + constructor(address: string, events: string[]) { + super( + `Could not find an event with any of the following topics in the logs for address ${address}: ${events.join( + ', ', + )}`, + () => + 'If the proxy was recently deployed, the transaction may not be available on Etherscan yet. Try running the verify task again after waiting a few blocks.', + ); + } +} /** * Indicates that the contract's bytecode does not match with the plugin's artifact. @@ -210,26 +232,59 @@ async function fullVerifyTransparentOrUUPS( const adminAddress = await getAdminAddress(provider, proxyAddress); if (!isEmptySlot(adminAddress)) { console.log(`Verifying proxy admin: ${adminAddress}`); - try { - await verifyWithArtifactOrFallback( - hre, - hardhatVerify, - etherscan, - adminAddress, - [verifiableContracts.proxyAdmin], - errorReport, - // The user provided the proxy address to verify, whereas this function is only verifying the related proxy admin. - // So even if this falls back and succeeds, we want to keep any errors that might have occurred while verifying the proxy itself. - false, + await verifyAdminOrFallback(hardhatVerify, etherscan, adminAddress, errorReport); + } + } + + /** + * Verifies a proxy admin contract by looking up an OwnershipTransferred event that should have been logged during construction + * to get the owner used for its constructor. + * + * This is different from the verifyWithArtifactOrFallback function because the proxy admin in Contracts 5.0 is not deployed directly by the plugin, + * but is deployed by the transparent proxy itself, so we cannot infer the admin's constructor arguments from the originating transaction's input bytecode. + */ + async function verifyAdminOrFallback( + hardhatVerify: (address: string) => Promise, + etherscan: Etherscan, + adminAddress: string, + errorReport: ErrorReport, + ) { + const attemptVerify = async () => { + let encodedOwner: string; + // Get the OwnershipTransferred event when the ProxyAdmin was created, which should have the encoded owner address as its second parameter (third topic). + const response = await getEventResponse(adminAddress, verifiableContracts.proxyAdmin.event, etherscan); + if (response === undefined) { + throw new EventsNotFound(adminAddress, [verifiableContracts.proxyAdmin.event]); + } else if (response.topics.length !== 3) { + throw new EventOrFunctionNotFound( + `Unexpected number of topics in event logs for ${verifiableContracts.proxyAdmin.event} from ${adminAddress}. Expected 3, got ${response.topics.length}: ${response.topics}`, + () => `The contract at ${adminAddress} does not appear to be a known proxy admin contract.`, ); - } catch (e: any) { - if (e instanceof EventNotFound) { - console.log( - 'Verification skipped for proxy admin - the admin address does not appear to contain a ProxyAdmin contract.', - ); - } + } else { + encodedOwner = response.topics[2].replace(/^0x/, ''); } - } + + const artifact = verifiableContracts.proxyAdmin.artifact; + const deployedBytecode = await getCode(provider, adminAddress); + if (deployedBytecode !== artifact.deployedBytecode) { + throw new BytecodeNotMatchArtifact( + `Bytecode does not match with the current version of ${artifact.contractName} in the Hardhat Upgrades plugin.`, + artifact.contractName, + ); + } + + await verifyContractWithConstructorArgs(etherscan, adminAddress, artifact, encodedOwner, errorReport); + }; + + await attemptVerifyOrFallback( + attemptVerify, + hardhatVerify, + adminAddress, + errorReport, + // The user provided the proxy address to verify, whereas this function is only verifying the related proxy admin. + // So even if this falls back and succeeds, we want to keep any errors that might have occurred while verifying the proxy itself. + false, + ); } async function verifyTransparentOrUUPS() { @@ -353,7 +408,7 @@ async function verifyImplementation( * @param possibleContractInfo An array of possible contract artifacts to use for verification along * with the corresponding creation event expected in the logs. * @returns the VerifiableContractInfo and txHash for the first event found - * @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. + * @throws {EventOrFunctionNotFound} if none of the events were found in the contract's logs according to Etherscan. */ async function searchEvent(etherscan: Etherscan, address: string, possibleContractInfo: VerifiableContractInfo[]) { for (let i = 0; i < possibleContractInfo.length; i++) { @@ -367,45 +422,36 @@ async function searchEvent(etherscan: Etherscan, address: string, possibleContra const events = possibleContractInfo.map(contractInfo => { return contractInfo.event; }); - throw new EventNotFound( - `Could not find an event with any of the following topics in the logs for address ${address}: ${events.join(', ')}`, - () => - 'If the proxy was recently deployed, the transaction may not be available on Etherscan yet. Try running the verify task again after waiting a few blocks.', - ); + throw new EventsNotFound(address, events); } /** - * Verifies a contract by matching with known artifacts. - * - * If a match was not found, falls back to verify directly using the regular hardhat verify task. + * Verifies a contract using the attemptVerify function. If it fails, falls back to verify directly using the regular hardhat verify task. * * If the fallback passes, logs as success. * If the fallback also fails, records errors for both the original and fallback attempts. * - * @param hre - * @param etherscan Etherscan instance + * @param attemptVerify A function that attempts to verify the contract. + * Should throw EventOrFunctionNotFound if the contract does not contain an expected event in its logs or function in its bytecode, + * or BytecodeNotMatchArtifact if the contract's bytecode does not match with the plugin's known artifact. + * @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command * @param address The contract address to verify - * @param possibleContractInfo An array of possible contract artifacts to use for verification along - * with the corresponding creation event expected in the logs. * @param errorReport Accumulated verification errors * @param convertErrorsToWarningsOnFallbackSuccess If fallback verification occurred and succeeded, whether any * previously accumulated errors should be converted into warnings in the final summary. - * @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. */ -async function verifyWithArtifactOrFallback( - hre: HardhatRuntimeEnvironment, +async function attemptVerifyOrFallback( + attemptVerify: () => Promise, hardhatVerify: (address: string) => Promise, - etherscan: Etherscan, address: string, - possibleContractInfo: VerifiableContractInfo[], errorReport: ErrorReport, convertErrorsToWarningsOnFallbackSuccess: boolean, ) { try { - await attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport); + await attemptVerify(); return true; } catch (origError: any) { - if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventNotFound) { + if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventOrFunctionNotFound) { // Try falling back to regular hardhat verify in case the source code is available in the user's project. try { await hardhatVerify(address); @@ -437,6 +483,43 @@ async function verifyWithArtifactOrFallback( } } +/** + * Verifies a contract by matching with known artifacts. + * + * If a match was not found, falls back to verify directly using the regular hardhat verify task. + * + * If the fallback passes, logs as success. + * If the fallback also fails, records errors for both the original and fallback attempts. + * + * @param hre + * @param etherscan Etherscan instance + * @param address The contract address to verify + * @param possibleContractInfo An array of possible contract artifacts to use for verification along + * with the corresponding creation event expected in the logs. + * @param errorReport Accumulated verification errors + * @param convertErrorsToWarningsOnFallbackSuccess If fallback verification occurred and succeeded, whether any + * previously accumulated errors should be converted into warnings in the final summary. + */ +async function verifyWithArtifactOrFallback( + hre: HardhatRuntimeEnvironment, + hardhatVerify: (address: string) => Promise, + etherscan: Etherscan, + address: string, + possibleContractInfo: VerifiableContractInfo[], + errorReport: ErrorReport, + convertErrorsToWarningsOnFallbackSuccess: boolean, +) { + const attemptVerify = () => + attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport); + return await attemptVerifyOrFallback( + attemptVerify, + hardhatVerify, + address, + errorReport, + convertErrorsToWarningsOnFallbackSuccess, + ); +} + /** * Attempts to verify a contract by looking up an event that should have been logged during contract construction, * finds the txHash for that, and infers the constructor args to use for verification. @@ -449,7 +532,7 @@ async function verifyWithArtifactOrFallback( * @param possibleContractInfo An array of possible contract artifacts to use for verification along * with the corresponding creation event expected in the logs. * @param errorReport Accumulated verification errors - * @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. + * @throws {EventOrFunctionNotFound} if none of the events were found in the contract's logs according to Etherscan. * @throws {BytecodeNotMatchArtifact} if the contract's bytecode does not match with the plugin's known artifact. */ async function attemptVerifyWithCreationEvent( @@ -530,17 +613,21 @@ async function verifyContractWithConstructorArgs( } /** - * Gets the txhash that created the contract at the given address, by calling the - * Etherscan API to look for an event that should have been emitted during construction. + * Calls the Etherscan API to look for an event that should have been emitted during construction + * of the contract at the given address, and returns the result corresponding to the first event found. * - * @param address The address to get the creation txhash for. + * @param address The address for which to get the event response. * @param topic The event topic string that should have been logged. * @param etherscan Etherscan instance - * @returns The txhash corresponding to the logged event, or undefined if not found or if + * @returns The event response, or undefined if not found or if * the address is not a contract. * @throws {UpgradesError} if the Etherscan API returned with not OK status */ -async function getContractCreationTxHash(address: string, topic: string, etherscan: Etherscan): Promise { +async function getEventResponse( + address: string, + topic: string, + etherscan: Etherscan, +): Promise { const params = { module: 'logs', action: 'getLogs', @@ -554,7 +641,7 @@ async function getContractCreationTxHash(address: string, topic: string, ethersc if (responseBody.status === RESPONSE_OK) { const result = responseBody.result; - return result[0].transactionHash; // get the txhash from the first instance of this event + return result[0]; } else if (responseBody.message === 'No records found' || responseBody.message === 'No logs found') { debug(`no result found for event topic ${topic} at address ${address}`); return undefined; @@ -566,6 +653,26 @@ async function getContractCreationTxHash(address: string, topic: string, ethersc } } +/** + * Gets the txhash that created the contract at the given address, by calling the + * Etherscan API to look for an event that should have been emitted during construction. + * + * @param address The address to get the creation txhash for. + * @param topic The event topic string that should have been logged. + * @param etherscan Etherscan instance + * @returns The txhash corresponding to the logged event, or undefined if not found or if + * the address is not a contract. + * @throws {UpgradesError} if the Etherscan API returned with not OK status + */ +async function getContractCreationTxHash(address: string, topic: string, etherscan: Etherscan): Promise { + const eventResponse = await getEventResponse(address, topic, etherscan); + if (eventResponse === undefined) { + return undefined; + } else { + return eventResponse.transactionHash; + } +} + /** * Calls the Etherscan API to link a proxy with its implementation ABI. * diff --git a/packages/plugin-hardhat/test/000_initial.js b/packages/plugin-hardhat/test/000_initial.js deleted file mode 100644 index 67b22c1b1..000000000 --- a/packages/plugin-hardhat/test/000_initial.js +++ /dev/null @@ -1,7 +0,0 @@ -const test = require('ava'); - -const { upgrades } = require('hardhat'); - -test('start with empty network manifest', async t => { - await t.throwsAsync(upgrades.admin.getInstance(), undefined, 'No ProxyAdmin was found in the network manifest'); -}); diff --git a/packages/plugin-hardhat/test/admin.js b/packages/plugin-hardhat/test/admin.js deleted file mode 100644 index 597b5a7c5..000000000 --- a/packages/plugin-hardhat/test/admin.js +++ /dev/null @@ -1,28 +0,0 @@ -const test = require('ava'); - -const { ethers, upgrades } = require('hardhat'); - -test.before(async t => { - t.context.Greeter = await ethers.getContractFactory('Greeter'); -}); - -test('deployProxyAdmin', async t => { - const { Greeter } = t.context; - - await t.throwsAsync(upgrades.admin.getInstance(), undefined, 'No ProxyAdmin was found in the network manifest'); - - const deployedAdminAddress = await upgrades.deployProxyAdmin(); - - const signer = (await ethers.getSigners())[1]; - const deployedAdminAddress2 = await upgrades.deployProxyAdmin(signer); - - t.is(deployedAdminAddress2, deployedAdminAddress); - - const adminInstance = await upgrades.admin.getInstance(); - - const greeter = await upgrades.deployProxy(Greeter, ['Hola admin!'], { kind: 'transparent' }); - const adminAddress = await adminInstance.getProxyAdmin(await greeter.getAddress()); - - t.is(await adminInstance.getAddress(), deployedAdminAddress); - t.is(await adminInstance.getAddress(), adminAddress); -}); diff --git a/packages/plugin-hardhat/test/beacon-initial-owner.js b/packages/plugin-hardhat/test/beacon-initial-owner.js new file mode 100644 index 000000000..7b6b8195e --- /dev/null +++ b/packages/plugin-hardhat/test/beacon-initial-owner.js @@ -0,0 +1,57 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter'); +}); + +test('initial owner using default signer', async t => { + const { Greeter } = t.context; + + const beacon = await upgrades.deployBeacon(Greeter); + const beaconOwner = await beacon.owner(); + + const defaultSigner = await ethers.provider.getSigner(0); + + t.is(beaconOwner, defaultSigner.address); +}); + +test('initial owner using custom signer', async t => { + const customSigner = await ethers.provider.getSigner(1); + + const Greeter = await ethers.getContractFactory('Greeter', customSigner); + + const beacon = await upgrades.deployBeacon(Greeter); + const beaconOwner = await beacon.owner(); + + t.is(beaconOwner, customSigner.address); +}); + +test('initial owner using initialOwner option', async t => { + const { Greeter } = t.context; + + const initialOwner = await ethers.provider.getSigner(2); + + const beacon = await upgrades.deployBeacon(Greeter, { initialOwner: initialOwner.address }); + const beaconOwner = await beacon.owner(); + + t.is(beaconOwner, initialOwner.address); +}); + +test('initial owner - no signer in ContractFactory', async t => { + const defaultProvider = ethers.getDefaultProvider(); + + const Greeter = await ethers.getContractFactory('Greeter', defaultProvider); + + await t.throwsAsync(upgrades.deployBeacon(Greeter), { + message: /Initial owner must be specified/, + }); + + const initialOwner = await ethers.provider.getSigner(2); + + const beacon = await upgrades.deployBeacon(Greeter, { initialOwner: initialOwner.address }); + const beaconOwner = await beacon.owner(); + + t.is(beaconOwner, initialOwner.address); +}); diff --git a/packages/plugin-hardhat/test/defender-deploy.js b/packages/plugin-hardhat/test/defender-deploy.js index 09ca2ad7c..8fc15e4b5 100644 --- a/packages/plugin-hardhat/test/defender-deploy.js +++ b/packages/plugin-hardhat/test/defender-deploy.js @@ -10,7 +10,7 @@ const { getBeaconProxyFactory, getTransparentUpgradeableProxyFactory, } = require('../dist/utils/factories'); -const artifactsBuildInfo = require('@openzeppelin/upgrades-core/artifacts/build-info.json'); +const artifactsBuildInfo = require('@openzeppelin/upgrades-core/artifacts/build-info-v5.json'); const TX_HASH = '0x1'; const DEPLOYMENT_ID = 'abc'; @@ -22,7 +22,7 @@ const SALT = 'customsalt'; const CREATE_FACTORY = '0x0000000000000000000000000000000000000010'; const LOGIC_ADDRESS = '0x0000000000000000000000000000000000000003'; -const ADMIN_ADDRESS = '0x0000000000000000000000000000000000000004'; +const INITIAL_OWNER_ADDRESS = '0x0000000000000000000000000000000000000004'; const DATA = '0x05'; test.beforeEach(async t => { @@ -317,7 +317,7 @@ test('calls defender deploy with TransparentUpgradeableProxy', async t => { const contractName = 'TransparentUpgradeableProxy'; const factory = await getTransparentUpgradeableProxyFactory(hre); - const result = await deploy.defenderDeploy(fakeHre, factory, {}, LOGIC_ADDRESS, ADMIN_ADDRESS, DATA); + const result = await deploy.defenderDeploy(fakeHre, factory, {}, LOGIC_ADDRESS, INITIAL_OWNER_ADDRESS, DATA); assertResult(t, result); sinon.assert.calledWithExactly(spy, { @@ -326,7 +326,7 @@ test('calls defender deploy with TransparentUpgradeableProxy', async t => { network: fakeChainId, artifactPayload: JSON.stringify(artifactsBuildInfo), licenseType: 'MIT', - constructorInputs: [LOGIC_ADDRESS, ADMIN_ADDRESS, DATA], + constructorInputs: [LOGIC_ADDRESS, INITIAL_OWNER_ADDRESS, DATA], verifySourceCode: true, relayerId: undefined, salt: undefined, diff --git a/packages/plugin-hardhat/test/import.js b/packages/plugin-hardhat/test/import-v4.js similarity index 90% rename from packages/plugin-hardhat/test/import.js rename to packages/plugin-hardhat/test/import-v4.js index 533ab36d7..9f2c1fb50 100644 --- a/packages/plugin-hardhat/test/import.js +++ b/packages/plugin-hardhat/test/import-v4.js @@ -14,9 +14,9 @@ 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('GreeterProxiable'); - t.context.GreeterV2Proxiable = await ethers.getContractFactory('GreeterV2Proxiable'); - t.context.GreeterV3Proxiable = await ethers.getContractFactory('GreeterV3Proxiable'); + t.context.GreeterProxiable = await ethers.getContractFactory('GreeterProxiable40'); + t.context.GreeterV2Proxiable = await ethers.getContractFactory('GreeterV2Proxiable40'); + t.context.GreeterV3Proxiable = await ethers.getContractFactory('GreeterV3Proxiable40'); t.context.CustomProxy = await ethers.getContractFactory('CustomProxy'); t.context.CustomProxyWithAdmin = await ethers.getContractFactory('CustomProxyWithAdmin'); @@ -32,12 +32,6 @@ test.before(async t => { 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 NOT_TRANSPARENT_PROXY = `doesn't look like a transparent proxy`; test('implementation happy path', async t => { @@ -75,7 +69,7 @@ test('transparent happy path', async t => { const proxy = await TransparentUpgradableProxy.deploy( await impl.getAddress(), await admin.getAddress(), - getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -96,7 +90,7 @@ test('uups happy path', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -119,7 +113,7 @@ test('beacon proxy happy path', async t => { await beacon.waitForDeployment(); const proxy = await BeaconProxy.deploy( await beacon.getAddress(), - getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -155,7 +149,7 @@ test('import proxy using contract instance', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -176,7 +170,7 @@ test('wrong kind', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -199,7 +193,7 @@ test('import custom UUPS proxy', async t => { await impl.waitForDeployment(); const proxy = await CustomProxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -216,7 +210,7 @@ test('import custom UUPS proxy with admin', async t => { await impl.waitForDeployment(); const proxy = await CustomProxyWithAdmin.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -236,7 +230,7 @@ test('wrong implementation', async t => { const proxy = await TransparentUpgradableProxy.deploy( await impl.getAddress(), await admin.getAddress(), - getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -255,7 +249,7 @@ test('multiple identical implementations', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -263,7 +257,7 @@ test('multiple identical implementations', async t => { await impl2.waitForDeployment(); const proxy2 = await ERC1967Proxy.deploy( await impl2.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat 2!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), ); await proxy2.waitForDeployment(); @@ -283,12 +277,12 @@ test('same implementation', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); const proxy2 = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat 2!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), ); await proxy2.waitForDeployment(); @@ -310,7 +304,7 @@ test('import transparents with different admin', async t => { const proxy = await TransparentUpgradableProxy.deploy( await impl.getAddress(), await admin.getAddress(), - getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -319,7 +313,7 @@ test('import transparents with different admin', async t => { const proxy2 = await TransparentUpgradableProxy.deploy( await impl.getAddress(), await admin2.getAddress(), - getInitializerData(Greeter.interface, ['Hello, Hardhat 2!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), ); await proxy2.waitForDeployment(); diff --git a/packages/plugin-hardhat/test/import-50.js b/packages/plugin-hardhat/test/import-v5.js similarity index 78% rename from packages/plugin-hardhat/test/import-50.js rename to packages/plugin-hardhat/test/import-v5.js index 35db16bc5..750ab93bc 100644 --- a/packages/plugin-hardhat/test/import-50.js +++ b/packages/plugin-hardhat/test/import-v5.js @@ -2,25 +2,23 @@ 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 TransparentUpgradableProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'); -const ERC1967Proxy = require('../artifacts/@openzeppelin/contracts-5.0/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'); +const ERC1967Proxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/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'); +const BeaconProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'); +const UpgradableBeacon = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/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.GreeterProxiable = await ethers.getContractFactory('GreeterProxiable'); + t.context.GreeterV2Proxiable = await ethers.getContractFactory('GreeterV2Proxiable'); 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, @@ -32,12 +30,6 @@ test.before(async t => { 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 NOT_TRANSPARENT_PROXY = `doesn't look like a transparent proxy`; test('implementation happy path', async t => { @@ -66,18 +58,16 @@ test('no contract', async t => { }); test('transparent happy path', async t => { - const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + const { Greeter, GreeterV2, TransparentUpgradableProxy } = t.context; - const owner = (await ethers.getSigners())[0]; + const owner = await ethers.provider.getSigner(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!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -98,7 +88,7 @@ test('uups happy path', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -115,7 +105,7 @@ test('uups happy path', async t => { test('beacon proxy happy path', async t => { const { Greeter, GreeterV2, UpgradableBeacon, BeaconProxy } = t.context; - const owner = (await ethers.getSigners())[0]; + const owner = await ethers.provider.getSigner(0); const impl = await Greeter.deploy(); await impl.waitForDeployment(); @@ -123,7 +113,7 @@ test('beacon proxy happy path', async t => { await beacon.waitForDeployment(); const proxy = await BeaconProxy.deploy( await beacon.getAddress(), - getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -141,7 +131,7 @@ test('beacon proxy happy path', async t => { test('beacon happy path', async t => { const { Greeter, GreeterV2, UpgradableBeacon } = t.context; - const owner = (await ethers.getSigners())[0]; + const owner = await ethers.provider.getSigner(0); const impl = await Greeter.deploy(); await impl.waitForDeployment(); @@ -161,7 +151,7 @@ test('import proxy using contract instance', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -182,7 +172,7 @@ test('wrong kind', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -205,7 +195,7 @@ test('import custom UUPS proxy', async t => { await impl.waitForDeployment(); const proxy = await CustomProxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -222,7 +212,7 @@ test('import custom UUPS proxy with admin', async t => { await impl.waitForDeployment(); const proxy = await CustomProxyWithAdmin.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -233,18 +223,16 @@ test('import custom UUPS proxy with admin', async t => { }); test('wrong implementation', async t => { - const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + const { Greeter, GreeterV2, TransparentUpgradableProxy } = t.context; - const owner = (await ethers.getSigners())[0]; + const owner = await ethers.provider.getSigner(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!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -263,7 +251,7 @@ test('multiple identical implementations', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -271,7 +259,7 @@ test('multiple identical implementations', async t => { await impl2.waitForDeployment(); const proxy2 = await ERC1967Proxy.deploy( await impl2.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat 2!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), ); await proxy2.waitForDeployment(); @@ -291,12 +279,12 @@ test('same implementation', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); const proxy2 = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat 2!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), ); await proxy2.waitForDeployment(); @@ -309,29 +297,25 @@ test('same implementation', async t => { }); test('import transparents with different admin', async t => { - const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + const { Greeter, GreeterV2, TransparentUpgradableProxy } = t.context; - const owner = (await ethers.getSigners())[0]; + const owner = await ethers.provider.getSigner(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!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); - const owner2 = (await ethers.getSigners())[1]; + const owner2 = await ethers.provider.getSigner(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!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), ); await proxy2.waitForDeployment(); @@ -349,18 +333,16 @@ test('import transparents with different admin', async t => { }); test('import transparent then upgrade with call', async t => { - const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + const { Greeter, GreeterV2, TransparentUpgradableProxy } = t.context; - const owner = (await ethers.getSigners())[0]; + const owner = await ethers.provider.getSigner(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!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); diff --git a/packages/plugin-hardhat/test/import-with-deploy.js b/packages/plugin-hardhat/test/import-with-deploy-v4.js similarity index 89% rename from packages/plugin-hardhat/test/import-with-deploy.js rename to packages/plugin-hardhat/test/import-with-deploy-v4.js index 03f9818fd..31177f0de 100644 --- a/packages/plugin-hardhat/test/import-with-deploy.js +++ b/packages/plugin-hardhat/test/import-with-deploy-v4.js @@ -10,8 +10,8 @@ const ERC1967Proxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppeli test.before(async t => { t.context.Greeter = await ethers.getContractFactory('Greeter'); t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2'); - t.context.GreeterProxiable = await ethers.getContractFactory('GreeterProxiable'); - t.context.GreeterV2Proxiable = await ethers.getContractFactory('GreeterV2Proxiable'); + t.context.GreeterProxiable = await ethers.getContractFactory('GreeterProxiable40'); + t.context.GreeterV2Proxiable = await ethers.getContractFactory('GreeterV2Proxiable40'); t.context.ProxyAdmin = await ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode); t.context.TransparentUpgradableProxy = await ethers.getContractFactory( @@ -22,12 +22,6 @@ test.before(async t => { t.context.ERC1967Proxy = await ethers.getContractFactory(ERC1967Proxy.abi, ERC1967Proxy.bytecode); }); -function getInitializerData(contractInterface, args) { - const initializer = 'initialize'; - const fragment = contractInterface.getFunction(initializer); - return contractInterface.encodeFunctionData(fragment, args); -} - test('import then deploy with same impl', async t => { const { GreeterProxiable, ERC1967Proxy } = t.context; @@ -35,7 +29,7 @@ test('import then deploy with same impl', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -62,7 +56,7 @@ test('deploy then import with same impl', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat 2!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), ); await proxy.waitForDeployment(); @@ -107,7 +101,7 @@ test('import previous import', async t => { await impl.waitForDeployment(); const proxy = await ERC1967Proxy.deploy( await impl.getAddress(), - getInitializerData(GreeterProxiable.interface, ['Hello, Hardhat!']), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); @@ -121,7 +115,7 @@ test('import previous import', async t => { ); }); -test('import then deploy transparent with same admin', async t => { +test('import then deploy transparent (manually) with same admin', async t => { const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; const impl = await Greeter.deploy(); @@ -131,12 +125,17 @@ test('import then deploy transparent with same admin', async t => { const proxy = await TransparentUpgradableProxy.deploy( await impl.getAddress(), await admin.getAddress(), - getInitializerData(Greeter.interface, ['Hello, Hardhat!']), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), ); await proxy.waitForDeployment(); const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); - const greeter2 = await upgrades.deployProxy(Greeter, ['Hello, Hardhat 2!']); + + const greeter2 = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + await admin.getAddress(), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), + ); await greeter2.waitForDeployment(); t.is( diff --git a/packages/plugin-hardhat/test/import-with-deploy-v5.js b/packages/plugin-hardhat/test/import-with-deploy-v5.js new file mode 100644 index 000000000..2158e7fd0 --- /dev/null +++ b/packages/plugin-hardhat/test/import-with-deploy-v5.js @@ -0,0 +1,145 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +const ProxyAdmin = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'); +const TransparentUpgradableProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'); + +const ERC1967Proxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter'); + t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2'); + t.context.GreeterProxiable = await ethers.getContractFactory('GreeterProxiable'); + t.context.GreeterV2Proxiable = await ethers.getContractFactory('GreeterV2Proxiable'); + + 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); +}); + +test('import then deploy with same impl', async t => { + const { GreeterProxiable, ERC1967Proxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + GreeterProxiable.interface.encodeFunctionData('initialize', ['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.deployProxy(GreeterProxiable, ['Hello, Hardhat 2!']); + await greeter2.waitForDeployment(); + t.is(await greeter2.greet(), 'Hello, Hardhat 2!'); + + t.is( + await upgrades.erc1967.getImplementationAddress(await greeter2.getAddress()), + await upgrades.erc1967.getImplementationAddress(await greeter.getAddress()), + ); +}); + +test('deploy then import with same impl', async t => { + const { GreeterProxiable, GreeterV2Proxiable, ERC1967Proxy } = t.context; + + const greeter = await upgrades.deployProxy(GreeterProxiable, ['Hello, Hardhat!']); + await greeter.waitForDeployment(); + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat 2!']), + ); + await proxy.waitForDeployment(); + + const greeter2 = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + t.is(await greeter2.greet(), 'Hello, Hardhat 2!'); + + const implAddr1 = await upgrades.erc1967.getImplementationAddress(await greeter.getAddress()); + const implAddr2 = await upgrades.erc1967.getImplementationAddress(await greeter2.getAddress()); + t.not(implAddr2, implAddr1); + + // upgrade imported proxy to the same impl + await upgrades.upgradeProxy(greeter2, GreeterProxiable); + const implAddrUpgraded = await upgrades.erc1967.getImplementationAddress(await greeter2.getAddress()); + t.true(implAddrUpgraded === implAddr1 || implAddrUpgraded === implAddr2, implAddrUpgraded); + + // upgrade imported proxy to different impl + await upgrades.upgradeProxy(greeter2, GreeterV2Proxiable); + const implAddrUpgraded2 = await upgrades.erc1967.getImplementationAddress(await greeter2.getAddress()); + t.not(implAddrUpgraded2 !== implAddrUpgraded, implAddrUpgraded2); +}); + +test('import previous deployment', async t => { + const { GreeterProxiable } = t.context; + + const greeter = await upgrades.deployProxy(GreeterProxiable, ['Hello, Hardhat!']); + await greeter.waitForDeployment(); + + const greeterImported = await upgrades.forceImport(await greeter.getAddress(), GreeterProxiable); + t.is(await greeterImported.greet(), 'Hello, Hardhat!'); + + t.is(await greeterImported.getAddress(), await greeter.getAddress()); + t.is( + await upgrades.erc1967.getImplementationAddress(await greeterImported.getAddress()), + await upgrades.erc1967.getImplementationAddress(await greeter.getAddress()), + ); +}); + +test('import previous import', async t => { + const { GreeterProxiable, ERC1967Proxy } = t.context; + + const impl = await GreeterProxiable.deploy(); + await impl.waitForDeployment(); + const proxy = await ERC1967Proxy.deploy( + await impl.getAddress(), + GreeterProxiable.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeterImported = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + const greeterImportedAgain = await upgrades.forceImport(await proxy.getAddress(), GreeterProxiable); + + t.is(await greeterImportedAgain.getAddress(), await greeterImported.getAddress()); + t.is( + await upgrades.erc1967.getImplementationAddress(await greeterImportedAgain.getAddress()), + await upgrades.erc1967.getImplementationAddress(await greeterImported.getAddress()), + ); +}); + +test('import then deploy transparent (with deployProxy) with different admin', async t => { + const { Greeter, GreeterV2, TransparentUpgradableProxy } = t.context; + + const owner = await ethers.provider.getSigner(0); + + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const proxy = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + owner, + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), + ); + await proxy.waitForDeployment(); + + const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); + const greeter2 = await upgrades.deployProxy(Greeter, ['Hello, Hardhat 2!']); + await greeter2.waitForDeployment(); + + t.not( + await upgrades.erc1967.getAdminAddress(await greeter2.getAddress()), + await upgrades.erc1967.getAdminAddress(await greeter.getAddress()), + ); + + const upgraded = await upgrades.upgradeProxy(await greeter.getAddress(), GreeterV2); + await upgraded.waitForDeployment(); + const upgraded2 = await upgrades.upgradeProxy(await greeter2.getAddress(), GreeterV2); + await upgraded2.waitForDeployment(); +}); diff --git a/packages/plugin-hardhat/test/infer-proxy-kind.js b/packages/plugin-hardhat/test/infer-proxy-kind.js index 7c264b925..002288216 100644 --- a/packages/plugin-hardhat/test/infer-proxy-kind.js +++ b/packages/plugin-hardhat/test/infer-proxy-kind.js @@ -14,10 +14,7 @@ test('infer proxy kind', async t => { t.is(await upgrades.erc1967.getAdminAddress(await uups.getAddress()), ethers.ZeroAddress); const transparent = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!']); - t.is( - await upgrades.erc1967.getAdminAddress(await transparent.getAddress()), - await (await upgrades.admin.getInstance()).getAddress(), - ); + t.not(await upgrades.erc1967.getAdminAddress(await transparent.getAddress()), ethers.ZeroAddress); const beacon = await upgrades.deployBeacon(Greeter); const beaconProxy = await upgrades.deployBeaconProxy(beacon, Greeter, ['Hello, Hardhat!']); diff --git a/packages/plugin-hardhat/test/timeout.js b/packages/plugin-hardhat/test/timeout.js index dd8ff38af..cbc6b022b 100644 --- a/packages/plugin-hardhat/test/timeout.js +++ b/packages/plugin-hardhat/test/timeout.js @@ -20,7 +20,6 @@ test.beforeEach(async () => { }); const TIMED_OUT_IMPL = 'Timed out waiting for implementation contract deployment'; -const TIMED_OUT_ADMIN = 'Timed out waiting for proxy admin contract deployment'; const USE_OPTIONS = 'If the problem persists, adjust the polling parameters with the timeout and pollingInterval options.'; @@ -31,7 +30,7 @@ test('timeout too low - beacon', async t => { t.true(error.message.includes(TIMED_OUT_IMPL) && error.message.includes(USE_OPTIONS), error.message); }); -test('timeout too low - proxy impl and admin', async t => { +test('timeout too low - proxy impl', async t => { // manual mining await network.provider.send('evm_setIntervalMining', [0]); @@ -47,15 +46,12 @@ test('timeout too low - proxy impl and admin', async t => { // mine the impl deployment await network.provider.send('evm_mine'); - // run again to continue with proxy admin - const error2 = await t.throwsAsync(() => - upgrades.deployProxy(t.context.Greeter, ['Hello, Hardhat!'], { - kind: 'transparent', - timeout: 1, - pollingInterval: 0, - }), - ); - t.true(error2.message.includes(TIMED_OUT_ADMIN) && error.message.includes(USE_OPTIONS), error2.message); + // run again to continue with proxy deployment + await upgrades.deployProxy(t.context.Greeter, ['Hello, Hardhat!'], { + kind: 'transparent', + timeout: 1, + pollingInterval: 0, + }); }); test('good timeout - beacon', async t => { diff --git a/packages/plugin-hardhat/test/transparent-admin-different-address.js b/packages/plugin-hardhat/test/transparent-admin-different-address.js deleted file mode 100644 index dd3889d81..000000000 --- a/packages/plugin-hardhat/test/transparent-admin-different-address.js +++ /dev/null @@ -1,34 +0,0 @@ -const test = require('ava'); - -const hre = require('hardhat'); -const { getProxyAdminFactory } = require('@openzeppelin/hardhat-upgrades/dist/utils/factories.js'); - -const { ethers, upgrades } = hre; - -test.before(async t => { - t.context.Greeter = await ethers.getContractFactory('Greeter'); - t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2'); -}); - -test('use different admin address than manifest', async t => { - // Deploy a proxy - const { Greeter, GreeterV2 } = t.context; - const greeter = await upgrades.deployProxy(Greeter, ['Hola admin!'], { kind: 'transparent' }); - - // Change to new admin owned by signer 2 - const [, signer] = await ethers.getSigners(); - const AdminFactory = await getProxyAdminFactory(hre, signer); - const deployedAdmin = await AdminFactory.deploy(); - await upgrades.admin.changeProxyAdmin(await greeter.getAddress(), await deployedAdmin.getAddress()); - - // Signer 1 cannot upgrade since it doesn't own the new admin - await t.throwsAsync(() => upgrades.upgradeProxy(greeter, GreeterV2)); - - // Upgrade using signer 2 - const GreeterV3 = Greeter.connect(signer); - await upgrades.upgradeProxy(greeter, GreeterV3); - - // Change the admin again, even though current admin is not the one in the manifest - const deployedAdmin2 = await AdminFactory.deploy(); - await upgrades.admin.changeProxyAdmin(await greeter.getAddress(), await deployedAdmin2.getAddress(), signer); -}); diff --git a/packages/plugin-hardhat/test/transparent-admin-initial-owner.js b/packages/plugin-hardhat/test/transparent-admin-initial-owner.js new file mode 100644 index 000000000..7d9751bcb --- /dev/null +++ b/packages/plugin-hardhat/test/transparent-admin-initial-owner.js @@ -0,0 +1,64 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); +const hre = require('hardhat'); + +const OWNABLE_ABI = ['function owner() view returns (address)']; + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter'); +}); + +test('initial owner using default signer', async t => { + const { Greeter } = t.context; + + const proxy = await upgrades.deployProxy(Greeter, ['hello']); + const adminAddress = await upgrades.erc1967.getAdminAddress(await proxy.getAddress()); + const admin = await hre.ethers.getContractAt(OWNABLE_ABI, adminAddress); + + const defaultSigner = await ethers.provider.getSigner(0); + + t.is(await admin.owner(), defaultSigner.address); +}); + +test('initial owner using custom signer', async t => { + const customSigner = await ethers.provider.getSigner(1); + + const Greeter = await ethers.getContractFactory('Greeter', customSigner); + + const proxy = await upgrades.deployProxy(Greeter, ['hello']); + const adminAddress = await upgrades.erc1967.getAdminAddress(await proxy.getAddress()); + const admin = await hre.ethers.getContractAt(OWNABLE_ABI, adminAddress); + + t.is(await admin.owner(), customSigner.address); +}); + +test('initial owner using initialOwner option', async t => { + const { Greeter } = t.context; + + const initialOwner = await ethers.provider.getSigner(2); + + const proxy = await upgrades.deployProxy(Greeter, ['hello'], { initialOwner: initialOwner.address }); + const adminAddress = await upgrades.erc1967.getAdminAddress(await proxy.getAddress()); + const admin = await hre.ethers.getContractAt(OWNABLE_ABI, adminAddress); + + t.is(await admin.owner(), initialOwner.address); +}); + +test('initial owner - no signer in ContractFactory', async t => { + const defaultProvider = ethers.getDefaultProvider(); + + const Greeter = await ethers.getContractFactory('Greeter', defaultProvider); + + await t.throwsAsync(upgrades.deployProxy(Greeter, ['hello']), { + message: /Initial owner must be specified/, + }); + + const initialOwner = await ethers.provider.getSigner(2); + + const proxy = await upgrades.deployProxy(Greeter, ['hello'], { initialOwner: initialOwner.address }); + const adminAddress = await upgrades.erc1967.getAdminAddress(await proxy.getAddress()); + const admin = await hre.ethers.getContractAt(OWNABLE_ABI, adminAddress); + + t.is(await admin.owner(), initialOwner.address); +}); diff --git a/packages/plugin-hardhat/test/transparent-admin-instance.js b/packages/plugin-hardhat/test/transparent-admin-instance.js deleted file mode 100644 index 76ec585e1..000000000 --- a/packages/plugin-hardhat/test/transparent-admin-instance.js +++ /dev/null @@ -1,15 +0,0 @@ -const test = require('ava'); - -const { ethers, upgrades } = require('hardhat'); - -test.before(async t => { - t.context.Greeter = await ethers.getContractFactory('Greeter'); -}); - -test('admin.getInstance', async t => { - const { Greeter } = t.context; - const greeter = await upgrades.deployProxy(Greeter, ['Hola admin!'], { kind: 'transparent' }); - const adminInstance = await upgrades.admin.getInstance(); - const adminAddress = await adminInstance.getProxyAdmin(await greeter.getAddress()); - t.is(await adminInstance.getAddress(), adminAddress); -}); diff --git a/packages/plugin-hardhat/test/transparent-change-admin-happy-path.js b/packages/plugin-hardhat/test/transparent-change-admin-happy-path.js deleted file mode 100644 index f51f1ef75..000000000 --- a/packages/plugin-hardhat/test/transparent-change-admin-happy-path.js +++ /dev/null @@ -1,19 +0,0 @@ -const test = require('ava'); - -const { ethers, upgrades, network } = require('hardhat'); -const { getAdminAddress } = require('@openzeppelin/upgrades-core'); - -const testAddress = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; - -test.before(async t => { - t.context.Greeter = await ethers.getContractFactory('Greeter'); -}); - -test('changeProxyAdmin', async t => { - const { Greeter } = t.context; - const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); - await upgrades.admin.changeProxyAdmin(await greeter.getAddress(), testAddress); - const newAdmin = await getAdminAddress(network.provider, await greeter.getAddress()); - - t.is(newAdmin, testAddress); -}); diff --git a/packages/plugin-hardhat/test/transparent-change-admin-signer.js b/packages/plugin-hardhat/test/transparent-change-admin-signer.js deleted file mode 100644 index 00507a46c..000000000 --- a/packages/plugin-hardhat/test/transparent-change-admin-signer.js +++ /dev/null @@ -1,17 +0,0 @@ -const test = require('ava'); - -const { ethers, upgrades, network } = require('hardhat'); -const { getAdminAddress } = require('@openzeppelin/upgrades-core'); - -const testAddress = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; - -test('changeProxyAdmin - signer', async t => { - const signer = (await ethers.getSigners())[1]; - const Greeter = await ethers.getContractFactory('Greeter', signer); - - const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); - await upgrades.admin.changeProxyAdmin(await greeter.getAddress(), testAddress, signer); - const newAdmin = await getAdminAddress(network.provider, await greeter.getAddress()); - - t.is(newAdmin, testAddress); -}); diff --git a/packages/plugin-hardhat/test/transparent-change-admin-wrong-signer.js b/packages/plugin-hardhat/test/transparent-change-admin-wrong-signer.js deleted file mode 100644 index 26579843e..000000000 --- a/packages/plugin-hardhat/test/transparent-change-admin-wrong-signer.js +++ /dev/null @@ -1,21 +0,0 @@ -const test = require('ava'); - -const { ethers, upgrades } = require('hardhat'); - -const testAddress = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; - -test.before(async t => { - t.context.Greeter = await ethers.getContractFactory('Greeter'); -}); - -test('changeProxyAdmin - wrong signer', async t => { - const { Greeter } = t.context; - const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); - - const signer = (await ethers.getSigners())[1]; - - const addr = await greeter.getAddress(); - await t.throwsAsync(() => upgrades.admin.changeProxyAdmin(addr, testAddress, signer), { - message: /(caller is not the owner)/, - }); -}); diff --git a/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-happy-path.js b/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-happy-path.js index c655fcb0e..185e6f9fa 100644 --- a/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-happy-path.js +++ b/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-happy-path.js @@ -1,9 +1,10 @@ const test = require('ava'); const hre = require('hardhat'); -const { getManifestAdmin } = require('@openzeppelin/hardhat-upgrades/dist/admin.js'); const { ethers, upgrades } = hre; -const testAddress = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; + +const TEST_ADDRESS = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; +const OWNABLE_ABI = ['function owner() view returns (address)']; test.before(async t => { t.context.Greeter = await ethers.getContractFactory('Greeter'); @@ -12,11 +13,13 @@ test.before(async t => { test('transferProxyAdminOwnership', async t => { // we need to deploy a proxy so we have a Proxy Admin const { Greeter } = t.context; - await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); + const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); + + await upgrades.admin.transferProxyAdminOwnership(await greeter.getAddress(), TEST_ADDRESS); - const admin = await getManifestAdmin(hre); - await upgrades.admin.transferProxyAdminOwnership(testAddress); + const adminAddress = await upgrades.erc1967.getAdminAddress(await greeter.getAddress()); + const admin = await hre.ethers.getContractAt(OWNABLE_ABI, adminAddress); const newOwner = await admin.owner(); - t.is(newOwner, testAddress); + t.is(newOwner, TEST_ADDRESS); }); diff --git a/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-signer.js b/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-signer.js index 6a5b2b6e7..b50015801 100644 --- a/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-signer.js +++ b/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-signer.js @@ -1,20 +1,22 @@ const test = require('ava'); const hre = require('hardhat'); -const { getManifestAdmin } = require('@openzeppelin/hardhat-upgrades/dist/admin.js'); const { ethers, upgrades } = hre; -const testAddress = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; + +const TEST_ADDRESS = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; +const OWNABLE_ABI = ['function owner() view returns (address)']; test('transferProxyAdminOwnership - signer', async t => { // we need to deploy a proxy so we have a Proxy Admin - const signer = (await ethers.getSigners())[1]; + const signer = await ethers.provider.getSigner(1); const Greeter = await ethers.getContractFactory('Greeter', signer); + const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); - await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); + await upgrades.admin.transferProxyAdminOwnership(await greeter.getAddress(), TEST_ADDRESS, signer); - const admin = await getManifestAdmin(hre); - await upgrades.admin.transferProxyAdminOwnership(testAddress, signer); + const adminAddress = await upgrades.erc1967.getAdminAddress(await greeter.getAddress()); + const admin = await hre.ethers.getContractAt(OWNABLE_ABI, adminAddress); const newOwner = await admin.owner(); - t.is(newOwner, testAddress); + t.is(newOwner, TEST_ADDRESS); }); diff --git a/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-wrong-signer.js b/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-wrong-signer.js index 224a03f28..ae5c86a9e 100644 --- a/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-wrong-signer.js +++ b/packages/plugin-hardhat/test/transparent-transfer-admin-ownership-wrong-signer.js @@ -11,11 +11,14 @@ test.before(async t => { test('transferProxyAdminOwnership - wrong signer', async t => { // we need to deploy a proxy so we have a Proxy Admin const { Greeter } = t.context; - await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); + const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { kind: 'transparent' }); - const signer = (await ethers.getSigners())[1]; + const signer = await ethers.provider.getSigner(1); - await t.throwsAsync(() => upgrades.admin.transferProxyAdminOwnership(testAddress, signer), { - message: /(caller is not the owner)/, - }); + const proxyAddress = await greeter.getAddress(); + + await t.throwsAsync( + () => upgrades.admin.transferProxyAdminOwnership(proxyAddress, testAddress, signer), + { message: /0x118cdaa7/ }, // bytes4(keccak256('OwnableUnauthorizedAccount(address)')) + ); }); diff --git a/packages/plugin-hardhat/test/transparent-v4-change-admin-different-address.js b/packages/plugin-hardhat/test/transparent-v4-change-admin-different-address.js new file mode 100644 index 000000000..e1ae3beee --- /dev/null +++ b/packages/plugin-hardhat/test/transparent-v4-change-admin-different-address.js @@ -0,0 +1,53 @@ +const test = require('ava'); + +const hre = require('hardhat'); +const ProxyAdmin = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'); +const TransparentUpgradableProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'); + +const { ethers, upgrades } = hre; + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter'); + t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2'); + + t.context.ProxyAdmin = await ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode); + t.context.TransparentUpgradableProxy = await ethers.getContractFactory( + TransparentUpgradableProxy.abi, + TransparentUpgradableProxy.bytecode, + ); +}); + +test('use different admin addresses', async t => { + const { Greeter, GreeterV2, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + // Deploy a v4 proxy and admin, and import them + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(); + await admin.waitForDeployment(); + const proxy = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + await admin.getAddress(), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), + ); + const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); + + // Change to new admin owned by signer 2 + const [signer1, signer2] = await ethers.getSigners(); + const ProxyAdminSigner2 = ProxyAdmin.connect(signer2); + const newAdmin = await ProxyAdminSigner2.deploy(); + + await upgrades.admin.changeProxyAdmin(await greeter.getAddress(), await newAdmin.getAddress(), signer1); + + // Signer 1 cannot upgrade since it doesn't own the new admin + await t.throwsAsync(() => upgrades.upgradeProxy(greeter, GreeterV2)); + + // Upgrade using signer 2 + const GreeterV3 = Greeter.connect(signer2); + await upgrades.upgradeProxy(greeter, GreeterV3); + + // Use the new admin to change the admin again, even though new admin is not the one in the manifest + const deployedAdmin2 = await ProxyAdmin.deploy(); + + await upgrades.admin.changeProxyAdmin(await greeter.getAddress(), await deployedAdmin2.getAddress(), signer2); +}); diff --git a/packages/plugin-hardhat/test/transparent-v4-change-admin-happy-path.js b/packages/plugin-hardhat/test/transparent-v4-change-admin-happy-path.js new file mode 100644 index 000000000..77aadee94 --- /dev/null +++ b/packages/plugin-hardhat/test/transparent-v4-change-admin-happy-path.js @@ -0,0 +1,38 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +const ProxyAdmin = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'); +const TransparentUpgradableProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'); + +const testAddress = '0x1E6876a6C2757de611c9F12B23211dBaBd1C9028'; + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter'); + t.context.ProxyAdmin = await ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode); + t.context.TransparentUpgradableProxy = await ethers.getContractFactory( + TransparentUpgradableProxy.abi, + TransparentUpgradableProxy.bytecode, + ); +}); + +test('changeProxyAdmin', async t => { + const { Greeter, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + // Deploy a v4 proxy and admin, and import them + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(); + await admin.waitForDeployment(); + const proxy = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + await admin.getAddress(), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), + ); + const greeter = await upgrades.forceImport(await proxy.getAddress(), Greeter); + + await upgrades.admin.changeProxyAdmin(await greeter.getAddress(), testAddress); + const newAdmin = await upgrades.erc1967.getAdminAddress(await greeter.getAddress()); + + t.is(newAdmin, testAddress); +}); diff --git a/packages/plugin-hardhat/test/transparent-v5-with-v4-manifest-admin.js b/packages/plugin-hardhat/test/transparent-v5-with-v4-manifest-admin.js new file mode 100644 index 000000000..1f7a7f73d --- /dev/null +++ b/packages/plugin-hardhat/test/transparent-v5-with-v4-manifest-admin.js @@ -0,0 +1,29 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); +const hre = require('hardhat'); + +const { fetchOrDeployAdmin } = require('@openzeppelin/upgrades-core'); +const { deploy } = require('../dist/utils'); + +const ProxyAdmin = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('Greeter'); + t.context.ProxyAdmin = await ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode); +}); + +test('add v4 admin to manifest, then deploy v5 transparent', async t => { + const { Greeter, ProxyAdmin } = t.context; + + const admin = await ProxyAdmin.deploy(); + await admin.waitForDeployment(); + + await fetchOrDeployAdmin(ethers.provider, () => deploy(hre, {}, ProxyAdmin), {}); + + const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!']); + await greeter.waitForDeployment(); + const greeterAdmin = await upgrades.erc1967.getAdminAddress(await greeter.getAddress()); + + t.not(greeterAdmin, await admin.getAddress()); +}); diff --git a/packages/plugin-hardhat/test/tx-overrides.js b/packages/plugin-hardhat/test/tx-overrides.js index b4449c268..a985ebcd2 100644 --- a/packages/plugin-hardhat/test/tx-overrides.js +++ b/packages/plugin-hardhat/test/tx-overrides.js @@ -2,9 +2,17 @@ const test = require('ava'); const { ethers, upgrades } = require('hardhat'); +const ProxyAdmin = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'); +const TransparentUpgradableProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'); + test.before(async t => { t.context.Greeter = await ethers.getContractFactory('Greeter'); t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2'); + t.context.ProxyAdmin = await ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode); + t.context.TransparentUpgradableProxy = await ethers.getContractFactory( + TransparentUpgradableProxy.abi, + TransparentUpgradableProxy.bytecode, + ); }); async function assertGasLimit(t, oldBlockNumber, expectedGasLimit, minExpectedBlocks) { @@ -108,8 +116,20 @@ test('prepareUpgrade', async t => { }); test('changeProxyAdmin', async t => { - const { Greeter } = t.context; - const greeter = await upgrades.deployProxy(Greeter, ['Hello']); + const { Greeter, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + // Deploy a v4 proxy and admin + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(); + await admin.waitForDeployment(); + const greeter = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + await admin.getAddress(), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), + ); + await greeter.waitForDeployment(); + const oldBlockNumber = await ethers.provider.getBlockNumber(); const [deployer, newAdmin] = await ethers.getSigners(); @@ -122,12 +142,25 @@ test('changeProxyAdmin', async t => { }); test('transferProxyAdminOwnership', async t => { - await upgrades.deployProxyAdmin(); + const { Greeter, ProxyAdmin, TransparentUpgradableProxy } = t.context; + + // Deploy a v4 proxy and admin + const impl = await Greeter.deploy(); + await impl.waitForDeployment(); + const admin = await ProxyAdmin.deploy(); + await admin.waitForDeployment(); + const greeter = await TransparentUpgradableProxy.deploy( + await impl.getAddress(), + await admin.getAddress(), + Greeter.interface.encodeFunctionData('initialize', ['Hello, Hardhat!']), + ); + await greeter.waitForDeployment(); + const oldBlockNumber = await ethers.provider.getBlockNumber(); const [deployer, newOwner] = await ethers.getSigners(); - await upgrades.admin.transferProxyAdminOwnership(newOwner.address, deployer, { + await upgrades.admin.transferProxyAdminOwnership(await greeter.getAddress(), newOwner.address, deployer, { txOverrides: { gasLimit: 10000009n }, }); diff --git a/packages/plugin-hardhat/test/uups-happy-path-50.js b/packages/plugin-hardhat/test/uups-happy-path-50.js deleted file mode 100644 index 389f3648c..000000000 --- a/packages/plugin-hardhat/test/uups-happy-path-50.js +++ /dev/null @@ -1,18 +0,0 @@ -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-v4.js b/packages/plugin-hardhat/test/uups-happy-path-v4.js new file mode 100644 index 000000000..0d1d73de2 --- /dev/null +++ b/packages/plugin-hardhat/test/uups-happy-path-v4.js @@ -0,0 +1,24 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('GreeterProxiable40'); + t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2Proxiable40'); + t.context.GreeterV3 = await ethers.getContractFactory('GreeterV3Proxiable40'); +}); + +test('happy path', async t => { + const { Greeter, GreeterV2, GreeterV3 } = 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(); + + const greeter3ImplAddr = await upgrades.prepareUpgrade(await greeter.getAddress(), GreeterV3); + const greeter3 = GreeterV3.attach(greeter3ImplAddr); + const version3 = await greeter3.version(); + t.is(version3, 'V3'); +}); diff --git a/packages/plugin-hardhat/test/uups-happy-path-with-call-50.js b/packages/plugin-hardhat/test/uups-happy-path-with-call-v4.js similarity index 91% rename from packages/plugin-hardhat/test/uups-happy-path-with-call-50.js rename to packages/plugin-hardhat/test/uups-happy-path-with-call-v4.js index 5e9f7b79a..191e47213 100644 --- a/packages/plugin-hardhat/test/uups-happy-path-with-call-50.js +++ b/packages/plugin-hardhat/test/uups-happy-path-with-call-v4.js @@ -3,8 +3,8 @@ 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'); + t.context.Greeter = await ethers.getContractFactory('GreeterProxiable40'); + t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2Proxiable40'); }); test('happy path - call with args', async t => { diff --git a/packages/plugin-hardhat/test/uups-initial-owner.js b/packages/plugin-hardhat/test/uups-initial-owner.js new file mode 100644 index 000000000..85ef0692c --- /dev/null +++ b/packages/plugin-hardhat/test/uups-initial-owner.js @@ -0,0 +1,17 @@ +const test = require('ava'); + +const { ethers, upgrades } = require('hardhat'); + +test.before(async t => { + t.context.Greeter = await ethers.getContractFactory('GreeterProxiable'); +}); + +test('uups with initialOwner option', async t => { + const { Greeter } = t.context; + + const initialOwner = await ethers.provider.getSigner(1); + + await t.throwsAsync(upgrades.deployProxy(Greeter, ['hello'], { initialOwner: initialOwner.address }), { + message: /The `initialOwner` option is not supported for this kind of proxy \('uups'\)/, + }); +}); diff --git a/yarn.lock b/yarn.lock index e8d68f11f..b5ed0aaac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@adraffy/ens-normalize@1.9.2": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz#60111a5d9db45b2e5cbb6231b0bb8d97e8659316" - integrity sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg== +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== "@ampproject/remapping@^2.2.0": version "2.2.1" @@ -1792,16 +1792,23 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" -"@noble/hashes@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" - integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -2378,20 +2385,15 @@ 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" - integrity sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg== +"@openzeppelin/contracts-upgradeable@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.0.tgz#859c00c55f04b6dda85b3c88bce507d65019888f" + integrity sha512-D54RHzkOKHQ8xUssPgQe2d/U92mwaiBDY7qCCVGq6VqwQjsT3KekEQ3bonev+BLP30oZ0R1U6YC8/oLpizgC5Q== -"@openzeppelin/contracts@4.8.3": - version "4.8.3" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.3.tgz#cbef3146bfc570849405f59cba18235da95a252a" - integrity sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg== +"@openzeppelin/contracts@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0.tgz#ee0e4b4564f101a5c4ee398cd4d73c0bd92b289c" + integrity sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw== "@openzeppelin/defender-admin-client@^1.52.0": version "1.52.0" @@ -2448,6 +2450,20 @@ lodash.startcase "^4.4.0" minimist "^1.2.0" +"@openzeppelin/upgrades-core-legacy@npm:@openzeppelin/upgrades-core@1.31.3", "@openzeppelin/upgrades-core@^1.30.0": + version "1.31.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.31.3.tgz#ff268a498fcd23106c33149e01dd3e7dfcae79fe" + integrity sha512-i7q0IuItKS4uO0clJwm4CARmt98aA9dLfKh38HFRbX+aFLGXwF0sOvB2iwr6f87ShH7d3DNuLrVgnnXUrYb7CA== + dependencies: + cbor "^9.0.0" + chalk "^4.1.0" + compare-versions "^6.0.0" + debug "^4.1.1" + ethereumjs-util "^7.0.3" + minimist "^1.2.7" + proper-lockfile "^4.1.1" + solidity-ast "^0.4.51" + "@parcel/watcher@2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" @@ -6234,14 +6250,14 @@ ethers@^5.0.13, ethers@^5.7.1, ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" -ethers@^6.6.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.6.7.tgz#9cf773bcbc0ca56d783d4774e9b73b4d1aff9962" - integrity sha512-1SdT3W5/IPAcx9l5/+9qKRYR/iqVIdNQIct18yeh+XvN+I4RK44mvOsAerMwJYCAwdQfsOgf3OkfozeuMInbtQ== +ethers@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.8.1.tgz#ee2a1a39b5f62a13678f90ccd879175391d0a2b4" + integrity sha512-iEKm6zox5h1lDn6scuRWdIdFJUCGg3+/aQWu0F4K0GVyEZiktFkqrJbRjTn1FlYEPz7RKA707D6g5Kdk6j7Ljg== dependencies: - "@adraffy/ens-normalize" "1.9.2" - "@noble/hashes" "1.1.2" - "@noble/secp256k1" "1.7.1" + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" "@types/node" "18.15.13" aes-js "4.0.0-beta.5" tslib "2.4.0"