From 87655f3f686f0d2384c66910ad189bdb4ec420dc Mon Sep 17 00:00:00 2001 From: nikola-bozin-txfusion Date: Mon, 19 Aug 2024 13:58:19 +0200 Subject: [PATCH 01/11] feat: introduce upgrades to hre - v5 --- .github/workflows/ci.yml | 12 +- examples/upgradable-example-l1/.eslintrc.js | 7 + .../.openzeppelin/sepolia.json | 421 ++++++++++++++++++ examples/upgradable-example-l1/README.md | 67 +++ .../upgradable-example-l1/contracts/Box.sol | 27 ++ .../contracts/BoxUups.sol | 33 ++ .../contracts/BoxUupsV2.sol | 54 +++ .../upgradable-example-l1/contracts/BoxV2.sol | 51 +++ .../upgradable-example-l1/contracts/Empty.sol | 5 + .../contracts/Factory.sol | 21 + .../contracts/FactoryUups.sol | 33 ++ .../contracts/FactoryUupsV2.sol | 53 +++ .../contracts/FactoryV2.sol | 44 ++ .../contracts/Greeter.sol | 16 + .../upgradable-example-l1/hardhat.config.ts | 36 ++ examples/upgradable-example-l1/package.json | 50 +++ .../scripts/deploy-box-beacon.ts | 17 + .../scripts/deploy-box-proxy.ts | 18 + .../scripts/deploy-box-uups.ts | 18 + .../scripts/upgrade-box-beacon.ts | 28 ++ .../scripts/upgrade-box-uups.ts | 22 + .../scripts/upgrade-box.ts | 22 + examples/upgradable-example-l1/tsconfig.json | 18 + .../hardhat-zksync-upgradable/package.json | 1 + .../src/core/function.ts | 2 +- .../src/extension-generator.ts | 160 +++++++ .../hardhat-zksync-upgradable/src/index.ts | 42 +- .../src/interfaces.ts | 99 +++- .../hardhat-zksync-upgradable/src/plugin.ts | 6 +- .../proxy-deployment/deploy-beacon-proxy.ts | 216 +++++---- .../src/proxy-deployment/deploy-beacon.ts | 132 ++++-- .../src/proxy-deployment/deploy-proxy.ts | 269 +++++++---- .../src/proxy-upgrade/upgrade-beacon.ts | 124 ++++-- .../src/proxy-upgrade/upgrade-proxy.ts | 177 +++++--- .../src/type-extensions.ts | 4 +- .../hardhat-zksync-upgradable/src/utils.ts | 35 +- .../src/utils/utils-general.ts | 14 + pnpm-lock.yaml | 260 ++++++++++- 38 files changed, 2229 insertions(+), 385 deletions(-) create mode 100644 examples/upgradable-example-l1/.eslintrc.js create mode 100644 examples/upgradable-example-l1/.openzeppelin/sepolia.json create mode 100644 examples/upgradable-example-l1/README.md create mode 100644 examples/upgradable-example-l1/contracts/Box.sol create mode 100644 examples/upgradable-example-l1/contracts/BoxUups.sol create mode 100644 examples/upgradable-example-l1/contracts/BoxUupsV2.sol create mode 100644 examples/upgradable-example-l1/contracts/BoxV2.sol create mode 100644 examples/upgradable-example-l1/contracts/Empty.sol create mode 100644 examples/upgradable-example-l1/contracts/Factory.sol create mode 100644 examples/upgradable-example-l1/contracts/FactoryUups.sol create mode 100644 examples/upgradable-example-l1/contracts/FactoryUupsV2.sol create mode 100644 examples/upgradable-example-l1/contracts/FactoryV2.sol create mode 100644 examples/upgradable-example-l1/contracts/Greeter.sol create mode 100644 examples/upgradable-example-l1/hardhat.config.ts create mode 100644 examples/upgradable-example-l1/package.json create mode 100644 examples/upgradable-example-l1/scripts/deploy-box-beacon.ts create mode 100644 examples/upgradable-example-l1/scripts/deploy-box-proxy.ts create mode 100644 examples/upgradable-example-l1/scripts/deploy-box-uups.ts create mode 100644 examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts create mode 100644 examples/upgradable-example-l1/scripts/upgrade-box-uups.ts create mode 100644 examples/upgradable-example-l1/scripts/upgrade-box.ts create mode 100644 examples/upgradable-example-l1/tsconfig.json create mode 100644 packages/hardhat-zksync-upgradable/src/extension-generator.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df2983e2a..befd79a06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,17 @@ jobs: pnpm hardhat run scripts/deploy-factory-uups.ts pnpm hardhat run scripts/upgrade-factory-beacon.ts pnpm hardhat run scripts/upgrade-factory-uups.ts - pnpm hardhat run scripts/upgrade-factory.ts + pnpm hardhat run scripts/upgrade-factory.ts + - name: Test upgradable example l1 + run: | + cd examples/upgradable-example + pnpm hardhat compile + pnpm hardhat run scripts/deploy-box-beacon.ts + pnpm hardhat run scripts/deploy-box-proxy.ts + pnpm hardhat run scripts/deploy-box-uups.ts + pnpm hardhat run scripts/upgrade-box-beacon.ts + pnpm hardhat run scripts/upgrade-box-uups.ts + pnpm hardhat run scripts/upgrade-box.ts - name: Show logs if: always() run: | diff --git a/examples/upgradable-example-l1/.eslintrc.js b/examples/upgradable-example-l1/.eslintrc.js new file mode 100644 index 000000000..d5b126e91 --- /dev/null +++ b/examples/upgradable-example-l1/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: [`${__dirname}/../../config/eslint/eslintrc.cjs`], + parserOptions: { + project: `${__dirname}/tsconfig.json`, + sourceType: "module", + }, + }; \ No newline at end of file diff --git a/examples/upgradable-example-l1/.openzeppelin/sepolia.json b/examples/upgradable-example-l1/.openzeppelin/sepolia.json new file mode 100644 index 000000000..0eb8096ea --- /dev/null +++ b/examples/upgradable-example-l1/.openzeppelin/sepolia.json @@ -0,0 +1,421 @@ +{ + "manifestVersion": "3.2", + "admin": { + "address": "0xD7fED3de4E423A7F37F2Be4928DFa95Cd5F4ccd4", + "txHash": "0x7ac43bdf9be81c9100a0debdcf782a52a2fbdf6dd918397be85629e4b892e245" + }, + "proxies": [ + { + "address": "0x35dFB618e5a16D4428740fE378bFc885c6921c5D", + "txHash": "0x88afcdf5fbc47c6293f1a08bb43d88f1a5af9d9160c512c5e7ef08c38ff89034", + "kind": "beacon" + }, + { + "address": "0x5093cc0c7a3121fF3a89015c7DF15B935Bf289A6", + "txHash": "0xe47112a8ac83ec7221cb4579687282119cf8160a7fa9d8ef24cc74082533d9a7", + "kind": "transparent" + }, + { + "address": "0xF564345d479e17135e4504706693953dC0BbF8A9", + "txHash": "0x8d3067173f04abe75b324c033128cd31fffa314fe3c66b01ee045bb951d3113c", + "kind": "uups" + }, + { + "address": "0x51f4c0076110a2797242723D8fB7388D22d55565", + "txHash": "0x8c9ea3dfec68e96ffc70b1b2047c4b94984a7fe34e34db1d34607a6bfc866292", + "kind": "uups" + }, + { + "address": "0x98437F6E0720d1a04140Afb6e941bda24Fc59ab7", + "txHash": "0x5cb9a46a7318a47377a319ef8cc10e762dcd5b5d7a065efd7fa2b9c021f969ab", + "kind": "beacon" + }, + { + "address": "0x70E0E49501EB642a0788A8b10D468680e8ceD32C", + "txHash": "0xd9deabca21d203b98a32ff2ecd77646da9920be635307237cb500f01da822a6e", + "kind": "beacon" + }, + { + "address": "0x58D453922c9036dcA3567A8837AaFeba9AAeb243", + "txHash": "0x08c1ebc63cada80c28bdfb524a588bba5a63c438d31a0ce2736a66a8c50d25ce", + "kind": "transparent" + }, + { + "address": "0x1e52885398D82968381397BFDB30F59Cc34417B7", + "txHash": "0x2c476e1c481749c4a166164ca0cc0de8eebfa67ded33fafa7a9460b9485428de", + "kind": "transparent" + }, + { + "address": "0xDbf63e4495e98877d0CEd3419f5476Be42e2837a", + "txHash": "0x1df840ad26f5669b038d6206e9412f6f59f3205e1e2c642908e10eae3e6a19f1", + "kind": "uups" + }, + { + "address": "0xbc1ae9571623ce46ab1d03e4920b0B8AB10b3442", + "txHash": "0xe4888682c8e8c016853880391e018a4fb4a6d614db96362a2d374c3fd19be8ee", + "kind": "uups" + } + ], + "impls": { + "dba45db98e4df9c5fde9024b207b830f291f1b722d86310f1517175ea9a3658d": { + "address": "0xe36C1a40021Fd0242EEd44eE2860e6Be9FF11D5b", + "txHash": "0x3a755ccd03ea153ca08673f0f14d67a2f3b21bad56e8d25bf4d4401796e6785a", + "layout": { + "solcVersion": "0.8.20", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "value", + "offset": 0, + "slot": "1", + "type": "t_uint256", + "contract": "Box", + "src": "contracts/Box.sol:7" + }, + { + "label": "secondValue", + "offset": 0, + "slot": "2", + "type": "t_uint256", + "contract": "Box", + "src": "contracts/Box.sol:8" + }, + { + "label": "thirdValue", + "offset": 0, + "slot": "3", + "type": "t_uint256", + "contract": "Box", + "src": "contracts/Box.sol:9" + } + ], + "types": { + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "4ea467f78617a083b45ec1794c89f8aff5f193eca64394a94cfc961b86c1b73f": { + "address": "0x5Cf8d6DfbfD588adB5Ef57a01E657D96C8377935", + "txHash": "0x330b5a87b8b4df51f021a2511ad74c13284d6c607d03ffadc376bf170cf3144d", + "layout": { + "solcVersion": "0.8.20", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "value", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "BoxUups", + "src": "contracts/BoxUups.sol:8" + }, + { + "label": "secondValue", + "offset": 0, + "slot": "202", + "type": "t_uint256", + "contract": "BoxUups", + "src": "contracts/BoxUups.sol:9" + }, + { + "label": "thirdValue", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "BoxUups", + "src": "contracts/BoxUups.sol:10" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "ffe53c56a4ef665c89f7119250ec79a1cfea3acb97611a8ae549da62ac7a6bd4": { + "address": "0x33F0E3c63f4df5dd63fab20e8e2e97D792DD7beF", + "txHash": "0x29ca8fdc92316b49d7c68042f0b4193bb0dae8a4e99e213d0eccad0a687a16a3", + "layout": { + "solcVersion": "0.8.20", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "value", + "offset": 0, + "slot": "1", + "type": "t_uint256", + "contract": "BoxV2", + "src": "contracts/BoxV2.sol:7" + }, + { + "label": "secondValue", + "offset": 0, + "slot": "2", + "type": "t_uint256", + "contract": "BoxV2", + "src": "contracts/BoxV2.sol:8" + }, + { + "label": "thirdValue", + "offset": 0, + "slot": "3", + "type": "t_uint256", + "contract": "BoxV2", + "src": "contracts/BoxV2.sol:9" + } + ], + "types": { + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "cc69a79a8a65be359f2171fe6a8dc4134811ec4fa66c48231744e99f843c0588": { + "address": "0x283A84Bf71Ab691c5AC32907D844ead26270F6c3", + "txHash": "0x5d47d17f5be67ed34e0b03b3e5aa8e847605bd3fa6a35f75cd5d96f87f5f20eb", + "layout": { + "solcVersion": "0.8.20", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "value", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "BoxUupsV2", + "src": "contracts/BoxUupsV2.sol:8" + }, + { + "label": "secondValue", + "offset": 0, + "slot": "202", + "type": "t_uint256", + "contract": "BoxUupsV2", + "src": "contracts/BoxUupsV2.sol:9" + }, + { + "label": "thirdValue", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "BoxUupsV2", + "src": "contracts/BoxUupsV2.sol:10" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + } + } +} diff --git a/examples/upgradable-example-l1/README.md b/examples/upgradable-example-l1/README.md new file mode 100644 index 000000000..d12a503af --- /dev/null +++ b/examples/upgradable-example-l1/README.md @@ -0,0 +1,67 @@ +# zkSync Era upgradable example + +This project demonstrates how to compile and deploy upgadable smart contracts in zkSync Era using the Hardhat plugins. + +## Prerequisites + +- node.js 14.x or later. +- yarn. + +## Configuration + +Plugin configuration is located in [`hardhat.config.ts`](./hardhat.config.ts). +You should only change the zkSync network configuration. + +`hardhat.config.ts` example with zkSync network configured with the name `zkTestnet` and `sepolia` used as the underlying layer 1 network: +```ts +import '@matterlabs/hardhat-zksync-solc'; +import '@matterlabs/hardhat-zksync-deploy'; +import '@matterlabs/hardhat-zksync-upgradable'; + +import { HardhatUserConfig } from 'hardhat/types'; + +const config: HardhatUserConfig = { + networks: { + sepolia: { + url: 'https://rpc.ankr.com/eth_sepolia' // you can use either the URL of the Ethereum Web3 RPC, or the identifier of the network (e.g. `mainnet` or `rinkeby`) + }, + zkTestnet: { + url: 'https://sepolia.era.zksync.dev', // you should use the URL of the zkSync network RPC + ethNetwork: 'sepolia', + zksync: true + }, + } +}; + +export default config; +``` + +If you don't specify zkSync network (`--network`), `local-setup` with (Ethereum RPC URL) and (zkSync RPC URL) will be used. + +## Usage + +Before using plugins, you need to build them first + +```sh +# Run the following in the *root* of the repo. +yarn +yarn build +``` + +After that you should be able to run plugins: + +```sh +# Run the following in `examples/upgradable-example` folder. +yarn +yarn hardhat compile +``` + +- `yarn hardhat compile`: compiles all the contracts in the `contracts` folder. + +To run a specific end-to-end script in the `scripts` folder, use the following command + +``` +yarn hardhad run ./scipts/ +``` + +- Example: `yarn hardhat run ./scripts/deploy-box-proxy.ts` \ No newline at end of file diff --git a/examples/upgradable-example-l1/contracts/Box.sol b/examples/upgradable-example-l1/contracts/Box.sol new file mode 100644 index 000000000..2c95bac7a --- /dev/null +++ b/examples/upgradable-example-l1/contracts/Box.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract Box is Initializable { + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + function initialize(uint256 initValue) public initializer { + value = initValue; + } + + // Reads the last stored value + function retrieve() public view returns (uint256) { + return value; + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); +} diff --git a/examples/upgradable-example-l1/contracts/BoxUups.sol b/examples/upgradable-example-l1/contracts/BoxUups.sol new file mode 100644 index 000000000..0b3345dd7 --- /dev/null +++ b/examples/upgradable-example-l1/contracts/BoxUups.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; +import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; +import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; + +contract BoxUups is Initializable, UUPSUpgradeable, OwnableUpgradeable { + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + function initialize(uint256 initValue) public initializer { + value = initValue; + __Ownable_init(); + __UUPSUpgradeable_init(); + } + + // Reads the last stored value + function retrieve() public view returns (uint256) { + return value; + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + + function _authorizeUpgrade(address) internal override onlyOwner {} + + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); +} diff --git a/examples/upgradable-example-l1/contracts/BoxUupsV2.sol b/examples/upgradable-example-l1/contracts/BoxUupsV2.sol new file mode 100644 index 000000000..3ae876bf9 --- /dev/null +++ b/examples/upgradable-example-l1/contracts/BoxUupsV2.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; +import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; +import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; + +contract BoxUupsV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + function initialize(uint256 initValue) public initializer { + value = initValue; + } + + // Reads the last stored value and returns it with a prefix + function retrieve() public view returns (string memory) { + return string(abi.encodePacked('V2: ', uint2str(value))); + } + + // Converts a uint to a string + function uint2str(uint _i) internal pure returns (string memory) { + if (_i == 0) { + return '0'; + } + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; + } + return string(bstr); + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + + function _authorizeUpgrade(address) internal override onlyOwner {} + + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); +} diff --git a/examples/upgradable-example-l1/contracts/BoxV2.sol b/examples/upgradable-example-l1/contracts/BoxV2.sol new file mode 100644 index 000000000..d451cb8d7 --- /dev/null +++ b/examples/upgradable-example-l1/contracts/BoxV2.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract BoxV2 is Initializable{ + uint256 private value; + uint256 private secondValue; + uint256 private thirdValue; + + // Emitted when the stored value changes + event ValueChanged(uint256 newValue); + + function initialize(uint256 initValue) public initializer { + value = initValue; + } + + // Stores a new value in the contract + function store(uint256 newValue) public { + value = newValue; + emit ValueChanged(newValue); + } + + // Reads the last stored value and returns it with a prefix + function retrieve() public view returns (string memory) { + return string(abi.encodePacked("V2: ", uint2str(value))); + } + + // Converts a uint to a string + function uint2str(uint _i) internal pure returns (string memory) { + if (_i == 0) { + return "0"; + } + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; + } + return string(bstr); + } +} diff --git a/examples/upgradable-example-l1/contracts/Empty.sol b/examples/upgradable-example-l1/contracts/Empty.sol new file mode 100644 index 000000000..42a9b4b85 --- /dev/null +++ b/examples/upgradable-example-l1/contracts/Empty.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract EmptyContract { +} \ No newline at end of file diff --git a/examples/upgradable-example-l1/contracts/Factory.sol b/examples/upgradable-example-l1/contracts/Factory.sol new file mode 100644 index 000000000..b0d4c2650 --- /dev/null +++ b/examples/upgradable-example-l1/contracts/Factory.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Empty.sol"; + +contract Factory { + address[] public deployedContracts; + + function initialize() public{ + deployEmptyContract(); + } + + function deployEmptyContract() public { + EmptyContract newContract = new EmptyContract(); + deployedContracts.push(address(newContract)); + } + + function getNumberOfDeployedContracts() public view returns (uint) { + return deployedContracts.length; + } +} diff --git a/examples/upgradable-example-l1/contracts/FactoryUups.sol b/examples/upgradable-example-l1/contracts/FactoryUups.sol new file mode 100644 index 000000000..ab7bc4610 --- /dev/null +++ b/examples/upgradable-example-l1/contracts/FactoryUups.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; +import "./Empty.sol"; + + +contract FactoryUups is Initializable, UUPSUpgradeable, OwnableUpgradeable { + address[] public deployedContracts; + + function initialize() public initializer{ + __Ownable_init(); + __UUPSUpgradeable_init(); + deployEmptyContract(); + } + + function deployEmptyContract() public { + EmptyContract newContract = new EmptyContract(); + deployedContracts.push(address(newContract)); + } + + function getNumberOfDeployedContracts() public view returns (uint) { + return deployedContracts.length; + } + + function storeNothing() public pure { + + } + + function _authorizeUpgrade(address) internal override onlyOwner {} +} diff --git a/examples/upgradable-example-l1/contracts/FactoryUupsV2.sol b/examples/upgradable-example-l1/contracts/FactoryUupsV2.sol new file mode 100644 index 000000000..98b55868d --- /dev/null +++ b/examples/upgradable-example-l1/contracts/FactoryUupsV2.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'; +import "./Empty.sol"; + + +contract FactoryUupsV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { + address[] public deployedContracts; + + function initialize() public initializer{ + deployEmptyContract(); + } + + function deployEmptyContract() public { + EmptyContract newContract = new EmptyContract(); + deployedContracts.push(address(newContract)); + } + + function getNumberOfDeployedContracts() public view returns (uint) { + return deployedContracts.length; + } + + function storeNothing() public pure { + + } + + function _authorizeUpgrade(address) internal override onlyOwner {} + + function uint2str(uint _i) internal pure returns (string memory) { + if (_i == 0) { + return '0'; + } + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; + } + return string(bstr); + } +} \ No newline at end of file diff --git a/examples/upgradable-example-l1/contracts/FactoryV2.sol b/examples/upgradable-example-l1/contracts/FactoryV2.sol new file mode 100644 index 000000000..0ad64cdea --- /dev/null +++ b/examples/upgradable-example-l1/contracts/FactoryV2.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Empty.sol"; + + +contract FactoryV2 { + address[] public deployedContracts; + + function initialize() public{ + deployEmptyContract(); + } + + function deployEmptyContract() public { + EmptyContract newContract = new EmptyContract(); + deployedContracts.push(address(newContract)); + } + + function getNumberOfDeployedContracts() public view returns (uint) { + return deployedContracts.length; + } + + function uint2str(uint _i) internal pure returns (string memory) { + if (_i == 0) { + return "0"; + } + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - (_i / 10) * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; + } + return string(bstr); + } +} diff --git a/examples/upgradable-example-l1/contracts/Greeter.sol b/examples/upgradable-example-l1/contracts/Greeter.sol new file mode 100644 index 000000000..0c86c024b --- /dev/null +++ b/examples/upgradable-example-l1/contracts/Greeter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +contract Greeter { + string greeting; + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } +} diff --git a/examples/upgradable-example-l1/hardhat.config.ts b/examples/upgradable-example-l1/hardhat.config.ts new file mode 100644 index 000000000..b84003f5b --- /dev/null +++ b/examples/upgradable-example-l1/hardhat.config.ts @@ -0,0 +1,36 @@ +import '@matterlabs/hardhat-zksync-upgradable'; +import "@nomiclabs/hardhat-ethers" + +import { HardhatUserConfig } from 'hardhat/config'; + +const config: HardhatUserConfig = { + zksolc: { + version: 'latest', + compilerSource: 'binary', + settings: { + optimizer: { + enabled: true, + }, + }, + }, + defaultNetwork:'sepolia', + networks: { + hardhat: { + zksync: false, + }, + eth: { + zksync: true, + url: 'http://0.0.0.0:8545', + }, + zkSyncNetwork: { + zksync: true, + ethNetwork: 'eth', + url: 'http://0.0.0.0:3050', + }, + }, + solidity: { + version: '0.8.20', + }, +}; + +export default config; \ No newline at end of file diff --git a/examples/upgradable-example-l1/package.json b/examples/upgradable-example-l1/package.json new file mode 100644 index 000000000..c0296bfc0 --- /dev/null +++ b/examples/upgradable-example-l1/package.json @@ -0,0 +1,50 @@ +{ + "name": "harhat-zksync-upgradable-example", + "version": "0.1.0", + "author": "Matter Labs", + "license": "MIT", + "scripts": { + "lint": "pnpm eslint", + "prettier:check": "pnpm prettier --check", + "lint:fix": "pnpm eslint --fix", + "fmt": "pnpm prettier --write", + "eslint": "eslint scripts/*.ts", + "prettier": "prettier scripts/*.ts", + "test": "mocha test/tests.ts --exit", + "build": "tsc --build .", + "clean": "rimraf dist" + }, + "devDependencies": { + "@openzeppelin/contracts": "^4.9.2", + "@types/node": "^18.11.17", + "@typescript-eslint/eslint-plugin": "^7.12.0", + "@typescript-eslint/parser": "^7.12.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-no-only-tests": "^3.1.0", + "eslint-plugin-prettier": "^5.0.1", + "prettier": "^3.3.0", + "rimraf": "^5.0.7", + "ts-node": "^10.9.2", + "typescript": "^5.3.0" + }, + "dependencies": { + "@matterlabs/hardhat-zksync-deploy": "workspace:^", + "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-upgradable": "workspace:^", + "@nomiclabs/hardhat-ethers": "^2.2.3", + "@openzeppelin/contracts-upgradeable": "^4.9.2", + "chalk": "^4.1.2", + "hardhat": "2.14.0", + "zksync": "^0.13.1", + "zksync-ethers": "^5.8.0" + }, + "prettier": { + "tabWidth": 4, + "printWidth": 120, + "parser": "typescript", + "singleQuote": true, + "bracketSpacing": true + } +} diff --git a/examples/upgradable-example-l1/scripts/deploy-box-beacon.ts b/examples/upgradable-example-l1/scripts/deploy-box-beacon.ts new file mode 100644 index 000000000..1e79e5d14 --- /dev/null +++ b/examples/upgradable-example-l1/scripts/deploy-box-beacon.ts @@ -0,0 +1,17 @@ +import * as hre from 'hardhat'; + +async function main() { + const Box = await hre.ethers.getContractFactory('Box'); + const box = await hre.upgrades.deployBeacon(Box); + await box.deployed(); + console.info(`Box deployed address: ${box.address}`); + + const boxBeaconProxy = await hre.upgrades.deployBeaconProxy(box, Box, [42]); + await boxBeaconProxy.deployed(); + console.info(`Box Beacon Proxy deployed address: ${boxBeaconProxy.address}`); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/upgradable-example-l1/scripts/deploy-box-proxy.ts b/examples/upgradable-example-l1/scripts/deploy-box-proxy.ts new file mode 100644 index 000000000..a25c22403 --- /dev/null +++ b/examples/upgradable-example-l1/scripts/deploy-box-proxy.ts @@ -0,0 +1,18 @@ +import chalk from 'chalk'; + +import * as hre from 'hardhat'; + +async function main() { + const Box = await hre.ethers.getContractFactory('Box'); + const box = await hre.upgrades.deployProxy(Box, [42]); + await box.deployed(); + console.info(`Box deployed address: ${box.address}`); + + const value = await box.retrieve(); + console.info(chalk.cyan('Box value is: ', value.toNumber())); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/upgradable-example-l1/scripts/deploy-box-uups.ts b/examples/upgradable-example-l1/scripts/deploy-box-uups.ts new file mode 100644 index 000000000..cae8389ac --- /dev/null +++ b/examples/upgradable-example-l1/scripts/deploy-box-uups.ts @@ -0,0 +1,18 @@ +import chalk from 'chalk'; + +import * as hre from 'hardhat'; + +async function main() { + const Box = await hre.ethers.getContractFactory('BoxUups'); + const box = await hre.upgrades.deployProxy(Box, [42], { initializer: 'initialize' }); + await box.deployed(); + console.info(`Box Uups deployed address: ${box.address}`); + + const value = await box.retrieve(); + console.info(chalk.cyan('Box value is: ', value.toNumber())); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts b/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts new file mode 100644 index 000000000..f920774c1 --- /dev/null +++ b/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts @@ -0,0 +1,28 @@ +import chalk from 'chalk'; + +import * as hre from 'hardhat'; + +async function main() { + const box = await hre.ethers.getContractFactory('Box'); + const beacon = await hre.upgrades.deployBeacon(box); + await beacon.deployed(); + + const boxBeaconProxy = await hre.upgrades.deployBeaconProxy(beacon, box, [42]); + await boxBeaconProxy.deployed(); + + // upgrade beacon + + const boxV2 = await hre.ethers.getContractFactory('Box'); + const boxV2Upgraded = await hre.upgrades.upgradeBeacon(beacon.address, boxV2); + console.info(chalk.green('Successfully upgraded beacon Box to BoxV2 on address: ', beacon.address)); + + // wait some time before the next call + await new Promise((resolve) => setTimeout(resolve, 2000)); + const value = await boxV2Upgraded.retrieve(); + console.info(chalk.cyan('New box value is', value)); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/upgradable-example-l1/scripts/upgrade-box-uups.ts b/examples/upgradable-example-l1/scripts/upgrade-box-uups.ts new file mode 100644 index 000000000..b5fde006d --- /dev/null +++ b/examples/upgradable-example-l1/scripts/upgrade-box-uups.ts @@ -0,0 +1,22 @@ +import chalk from 'chalk'; + +import * as hre from 'hardhat'; + +async function main() { + const contractName = 'BoxUups'; + + const contract = await hre.ethers.getContractFactory(contractName); + const box = await hre.upgrades.deployProxy(contract, [42], { initializer: 'initialize' }); + console.info(chalk.green(`Deployed BoxUups to ${box.address}`)); + + await box.deployed(); + + const BoxUupsV2 = await hre.ethers.getContractFactory('BoxUupsV2'); + await hre.upgrades.upgradeProxy(box.address, BoxUupsV2); + console.info(chalk.green('Successfully upgraded BoxUups to BoxUupsV2')); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/upgradable-example-l1/scripts/upgrade-box.ts b/examples/upgradable-example-l1/scripts/upgrade-box.ts new file mode 100644 index 000000000..e81bc374e --- /dev/null +++ b/examples/upgradable-example-l1/scripts/upgrade-box.ts @@ -0,0 +1,22 @@ +import chalk from 'chalk'; + +import * as hre from 'hardhat'; + +async function main() { + const Box = await hre.ethers.getContractFactory('Box'); + const box = await hre.upgrades.deployProxy(Box, [42], { initializer: 'store' }); + + await box.deployed(); + console.info(chalk.green(`Deployed Box to ${box.address}`)); + + // upgrade proxy implementation + + const BoxV2 = await hre.ethers.getContractFactory('BoxV2'); + await hre.upgrades.upgradeProxy(box.address, BoxV2); + console.info(chalk.green('Successfully upgraded Box to BoxV2')); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/upgradable-example-l1/tsconfig.json b/examples/upgradable-example-l1/tsconfig.json new file mode 100644 index 000000000..2b2998046 --- /dev/null +++ b/examples/upgradable-example-l1/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "outDir": "dist" + }, + "include": [ + "./hardhat.config.ts", + "./scripts", + "scripts", + "./test", + "typechain/**/*" + ] +} diff --git a/packages/hardhat-zksync-upgradable/package.json b/packages/hardhat-zksync-upgradable/package.json index 607e185a7..0b1157429 100644 --- a/packages/hardhat-zksync-upgradable/package.json +++ b/packages/hardhat-zksync-upgradable/package.json @@ -36,6 +36,7 @@ "@matterlabs/hardhat-zksync-deploy": "workspace:^", "@matterlabs/hardhat-zksync-solc": "^1.2.0", "@openzeppelin/upgrades-core": "~1.29.0", + "@openzeppelin/hardhat-upgrades": "^1.28.0", "@openzeppelin/contracts-hardhat-zksync-upgradable": "npm:@openzeppelin/contracts@^4.9.2", "chalk": "^4.1.2", "fs-extra": "^11.2.0", diff --git a/packages/hardhat-zksync-upgradable/src/core/function.ts b/packages/hardhat-zksync-upgradable/src/core/function.ts index bf59dbfc2..54ef263dd 100644 --- a/packages/hardhat-zksync-upgradable/src/core/function.ts +++ b/packages/hardhat-zksync-upgradable/src/core/function.ts @@ -1,5 +1,5 @@ import type { FunctionDefinition, TypeName, VariableDeclaration } from 'solidity-ast'; -import { ASTDereferencer } from '@openzeppelin/upgrades-core/dist/ast-dereferencer'; +import { ASTDereferencer } from 'solidity-ast/utils'; import assert from 'assert'; function serializeParameterType(parameter: VariableDeclaration, deref: ASTDereferencer): string { diff --git a/packages/hardhat-zksync-upgradable/src/extension-generator.ts b/packages/hardhat-zksync-upgradable/src/extension-generator.ts new file mode 100644 index 000000000..16bcd7ea2 --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/extension-generator.ts @@ -0,0 +1,160 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { lazyObject } from 'hardhat/plugins'; +import { wrapMakeFunction } from './utils'; +import { HardhatUpgrades, HardhatUpgradesOZ, makeUndefinedFunction, PlatformHardhatUpgrades } from './interfaces'; + +interface Generator { + populateExtension(): any; +} + +export class ExtensionGenerator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + public populatedExtension(): any { + if (this._hre.network.zksync) { + const zkSyncGenerator = new ZkSyncGenerator(this._hre); + return zkSyncGenerator.populateExtension(); + } else { + const openzeppelinGenerator = new OpenZeppelinGenerator(this._hre); + return openzeppelinGenerator.populateExtension(); + } + } +} + +class ZkSyncGenerator implements Generator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + private makeFunctions(): HardhatUpgrades { + const { makeDeployProxy } = require('./proxy-deployment/deploy-proxy'); + const { makeUpgradeProxy } = require('./proxy-upgrade/upgrade-proxy'); + const { makeValidateImplementation } = require('./validations/validate-implementation'); + const { makeDeployBeacon } = require('./proxy-deployment/deploy-beacon'); + const { makeDeployBeaconProxy } = require('./proxy-deployment/deploy-beacon-proxy'); + const { makeUpgradeBeacon } = require('./proxy-upgrade/upgrade-beacon'); + const { makeDeployProxyAdmin } = require('./proxy-deployment/deploy-proxy-admin'); + const { makeEstimateGasProxy } = require('./gas-estimation/estimate-gas-proxy'); + const { makeEstimateGasBeacon } = require('./gas-estimation/estimate-gas-beacon'); + const { makeEstimateGasBeaconProxy } = require('./gas-estimation/estimate-gas-beacon-proxy'); + const { makeGetInstanceFunction, makeChangeProxyAdmin, makeTransferProxyAdminOwnership } = require('./admin'); + return { + deployProxy: wrapMakeFunction(this._hre, makeDeployProxy(this._hre)), + upgradeProxy: wrapMakeFunction(this._hre, makeUpgradeProxy(this._hre)), + validateImplementation: wrapMakeFunction(this._hre, makeValidateImplementation(this._hre)), + deployBeacon: wrapMakeFunction(this._hre, makeDeployBeacon(this._hre)), + deployBeaconProxy: wrapMakeFunction(this._hre, makeDeployBeaconProxy(this._hre)), + upgradeBeacon: wrapMakeFunction(this._hre, makeUpgradeBeacon(this._hre)), + deployProxyAdmin: wrapMakeFunction(this._hre, makeDeployProxyAdmin(this._hre)), + admin: { + getInstance: wrapMakeFunction(this._hre, makeGetInstanceFunction(this._hre)), + changeProxyAdmin: wrapMakeFunction(this._hre, makeChangeProxyAdmin(this._hre)), + transferProxyAdminOwnership: wrapMakeFunction(this._hre, makeTransferProxyAdminOwnership(this._hre)), + }, + estimation: { + estimateGasProxy: wrapMakeFunction(this._hre, makeEstimateGasProxy(this._hre)), + estimateGasBeacon: wrapMakeFunction(this._hre, makeEstimateGasBeacon(this._hre)), + estimateGasBeaconProxy: wrapMakeFunction(this._hre, makeEstimateGasBeaconProxy(this._hre)), + }, + forceImport: makeUndefinedFunction(), + silenceWarnings: makeUndefinedFunction(), + validateUpgrade: makeUndefinedFunction(), + deployImplementation: makeUndefinedFunction(), + prepareUpgrade: makeUndefinedFunction(), + beacon: { + getImplementationAddress: makeUndefinedFunction(), + }, + erc1967: { + getAdminAddress: makeUndefinedFunction(), + getImplementationAddress: makeUndefinedFunction(), + getBeaconAddress: makeUndefinedFunction(), + }, + }; + } + + public populateExtension(): any { + this._hre.upgrades = lazyObject(() => this.makeFunctions() as HardhatUpgrades & HardhatUpgradesOZ); + this._hre.zkUpgrades = lazyObject(() => this.makeFunctions()); + } +} + +class OpenZeppelinGenerator implements Generator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + public populateExtension(): void { + this._hre.upgrades = lazyObject(() => this.makeFunctions(false)); + this._hre.platform = lazyObject(() => this.makePlatformFunctions(this._hre)); + } + + private makeFunctions(platform: boolean) { + const { + silenceWarnings, + getAdminAddress, + getImplementationAddress, + getBeaconAddress, + } = require('@openzeppelin/upgrades-core'); + const { makeDeployProxy } = require('@openzeppelin/hardhat-upgrades/dist/deploy-proxy'); + const { makeDeployProxyAdmin } = require('@openzeppelin/hardhat-upgrades/dist/deploy-proxy-admin'); + const { makeUpgradeProxy } = require('@openzeppelin/hardhat-upgrades/dist/upgrade-proxy'); + const { makeValidateImplementation } = require('@openzeppelin/hardhat-upgrades/dist/validate-implementation'); + const { makeValidateUpgrade } = require('@openzeppelin/hardhat-upgrades/dist/validate-upgrade'); + const { makeDeployImplementation } = require('@openzeppelin/hardhat-upgrades/dist/deploy-implementation'); + const { makePrepareUpgrade } = require('@openzeppelin/hardhat-upgrades/dist/prepare-upgrade'); + const { makeDeployBeacon } = require('@openzeppelin/hardhat-upgrades/dist/deploy-beacon'); + const { makeDeployBeaconProxy } = require('@openzeppelin/hardhat-upgrades/dist/deploy-beacon-proxy'); + const { makeUpgradeBeacon } = require('@openzeppelin/hardhat-upgrades/dist/upgrade-beacon'); + const { makeForceImport } = require('@openzeppelin/hardhat-upgrades/dist/force-import'); + const { + makeChangeProxyAdmin, + makeTransferProxyAdminOwnership, + makeGetInstanceFunction, + } = require('@openzeppelin/hardhat-upgrades/dist/admin'); + const { getImplementationAddressFromBeacon } = require('@openzeppelin/upgrades-core/dist/impl-address'); + + return { + silenceWarnings, + deployProxy: makeDeployProxy(this._hre, platform), + upgradeProxy: makeUpgradeProxy(this._hre, platform), // block on platform + validateImplementation: makeValidateImplementation(this._hre), + validateUpgrade: makeValidateUpgrade(this._hre), + deployImplementation: makeDeployImplementation(this._hre, platform), + prepareUpgrade: makePrepareUpgrade(this._hre, platform), + deployBeacon: makeDeployBeacon(this._hre, platform), // block on platform + deployBeaconProxy: makeDeployBeaconProxy(this._hre, platform), + upgradeBeacon: makeUpgradeBeacon(this._hre, platform), // block on platform + deployProxyAdmin: makeDeployProxyAdmin(this._hre, platform), // block on platform + forceImport: makeForceImport(this._hre), + admin: { + getInstance: makeGetInstanceFunction(this._hre), + changeProxyAdmin: makeChangeProxyAdmin(this._hre, platform), // block on platform + transferProxyAdminOwnership: makeTransferProxyAdminOwnership(this._hre, platform), // block on platform + }, + erc1967: { + getAdminAddress: (proxyAddress: string) => getAdminAddress(this._hre.network.provider, proxyAddress), + getImplementationAddress: (proxyAddress: string) => + getImplementationAddress(this._hre.network.provider, proxyAddress), + getBeaconAddress: (proxyAddress: string) => getBeaconAddress(this._hre.network.provider, proxyAddress), + }, + beacon: { + getImplementationAddress: (beaconAddress: string) => + getImplementationAddressFromBeacon(this._hre.network.provider, beaconAddress), + }, + estimation: { + estimateGasProxy: makeUndefinedFunction(), + estimateGasBeacon: makeUndefinedFunction(), + estimateGasBeaconProxy: makeUndefinedFunction(), + }, + }; + } + + private makePlatformFunctions(hre: HardhatRuntimeEnvironment): PlatformHardhatUpgrades { + const { makeDeployContract } = require('./deploy-contract'); + const { makeProposeUpgrade } = require('./platform/propose-upgrade'); + const { makeGetDefaultApprovalProcess } = require('./platform/get-default-approval-process'); + + return { + ...this.makeFunctions(true), + deployContract: makeDeployContract(hre, true), + proposeUpgrade: makeProposeUpgrade(hre, true), + getDefaultApprovalProcess: makeGetDefaultApprovalProcess(hre), + }; + } +} diff --git a/packages/hardhat-zksync-upgradable/src/index.ts b/packages/hardhat-zksync-upgradable/src/index.ts index 36457a0d5..84b38cf12 100644 --- a/packages/hardhat-zksync-upgradable/src/index.ts +++ b/packages/hardhat-zksync-upgradable/src/index.ts @@ -8,8 +8,7 @@ import { TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, } from 'hardhat/builtin-tasks/task-names'; -import { lazyObject } from 'hardhat/plugins'; -import { HardhatUpgrades, RunCompilerArgs } from './interfaces'; +import { RunCompilerArgs } from './interfaces'; import { extendCompilerOutputSelection, isFullZkSolcOutput } from './utils/utils-general'; import { validate } from './core/validate'; import { deployZkSyncBeacon, deployZkSyncProxy, upgradeZkSyncBeacon, upgradeZkSyncProxy } from './task-actions'; @@ -19,42 +18,13 @@ import { TASK_UPGRADE_ZKSYNC_BEACON, TASK_UPGRADE_ZKSYNC_PROXY, } from './task-names'; -import { checkOpenzeppelinVersions, getUpgradableContracts } from './utils'; +import { getUpgradableContracts } from './utils'; -extendEnvironment((hre) => { - hre.zkUpgrades = lazyObject((): HardhatUpgrades => { - const { makeDeployProxy } = require('./proxy-deployment/deploy-proxy'); - const { makeUpgradeProxy } = require('./proxy-upgrade/upgrade-proxy'); - const { makeValidateImplementation } = require('./validations/validate-implementation'); - const { makeDeployBeacon } = require('./proxy-deployment/deploy-beacon'); - const { makeDeployBeaconProxy } = require('./proxy-deployment/deploy-beacon-proxy'); - const { makeUpgradeBeacon } = require('./proxy-upgrade/upgrade-beacon'); - const { makeDeployProxyAdmin } = require('./proxy-deployment/deploy-proxy-admin'); - const { makeEstimateGasProxy } = require('./gas-estimation/estimate-gas-proxy'); - const { makeEstimateGasBeacon } = require('./gas-estimation/estimate-gas-beacon'); - const { makeEstimateGasBeaconProxy } = require('./gas-estimation/estimate-gas-beacon-proxy'); - const { makeGetInstanceFunction, makeChangeProxyAdmin, makeTransferProxyAdminOwnership } = require('./admin'); - return { - deployProxy: checkOpenzeppelinVersions(makeDeployProxy(hre)), - upgradeProxy: checkOpenzeppelinVersions(makeUpgradeProxy(hre)), - validateImplementation: checkOpenzeppelinVersions(makeValidateImplementation(hre)), - deployBeacon: checkOpenzeppelinVersions(makeDeployBeacon(hre)), - deployBeaconProxy: checkOpenzeppelinVersions(makeDeployBeaconProxy(hre)), - upgradeBeacon: checkOpenzeppelinVersions(makeUpgradeBeacon(hre)), - deployProxyAdmin: checkOpenzeppelinVersions(makeDeployProxyAdmin(hre)), - admin: { - getInstance: checkOpenzeppelinVersions(makeGetInstanceFunction(hre)), - changeProxyAdmin: checkOpenzeppelinVersions(makeChangeProxyAdmin(hre)), - transferProxyAdminOwnership: checkOpenzeppelinVersions(makeTransferProxyAdminOwnership(hre)), - }, - estimation: { - estimateGasProxy: checkOpenzeppelinVersions(makeEstimateGasProxy(hre)), - estimateGasBeacon: checkOpenzeppelinVersions(makeEstimateGasBeacon(hre)), - estimateGasBeaconProxy: checkOpenzeppelinVersions(makeEstimateGasBeaconProxy(hre)), - }, - }; - }); +import { ExtensionGenerator } from './extension-generator'; +extendEnvironment((hre) => { + const extesionGenerator = new ExtensionGenerator(hre); + extesionGenerator.populatedExtension(); hre.config.solidity.compilers.forEach((compiler) => { extendCompilerOutputSelection(compiler); }); diff --git a/packages/hardhat-zksync-upgradable/src/interfaces.ts b/packages/hardhat-zksync-upgradable/src/interfaces.ts index d78435d7e..fc1a2258f 100644 --- a/packages/hardhat-zksync-upgradable/src/interfaces.ts +++ b/packages/hardhat-zksync-upgradable/src/interfaces.ts @@ -2,29 +2,87 @@ import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'; import * as zk from 'zksync-ethers'; -import { DeployAdminFunction } from './proxy-deployment/deploy-proxy-admin'; -import { UpgradeFunction } from './proxy-upgrade/upgrade-proxy'; -import { DeployBeaconFunction } from './proxy-deployment/deploy-beacon'; -import { DeployBeaconProxyFunction } from './proxy-deployment/deploy-beacon-proxy'; -import { UpgradeBeaconFunction } from './proxy-upgrade/upgrade-beacon'; -import { DeployFunction } from './proxy-deployment/deploy-proxy'; -import { ValidateImplementationOptions } from './utils/options'; -import { ChangeAdminFunction, GetInstanceFunction, TransferProxyAdminOwnershipFunction } from './admin'; -import { EstimateBeaconGasFunction } from './gas-estimation/estimate-gas-beacon-proxy'; +import { DeployFunction as DeployFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-proxy'; +import { UpgradeFunction as UpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/upgrade-proxy'; +import { ValidateUpgradeFunction as ValidateUpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/validate-upgrade'; +import { DeployImplementationFunction as DeployImplementationFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-implementation'; +import { PrepareUpgradeFunction as PrepareUpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/prepare-upgrade'; +import { DeployBeaconFunction as DeployBeaconFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-beacon'; +import { DeployBeaconProxyFunction as DeployBeaconProxyFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-beacon-proxy'; +import { UpgradeBeaconFunction as UpgradeBeaconFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/upgrade-beacon'; +import { ForceImportFunction as ForceImportFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/force-import'; +import { DeployContractFunction as DeployContractFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-contract'; +import { GetDefaultApprovalProcessFunction as GetDefaultApprovalProcessFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/platform/get-default-approval-process'; +import { ValidateImplementationFunction as ValidateImplementationFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/validate-implementation'; import { EstimateProxyGasFunction } from './gas-estimation/estimate-gas-proxy'; +import { EstimateBeaconGasFunction } from './gas-estimation/estimate-gas-beacon-proxy'; +import { ChangeAdminFunction, GetInstanceFunction, TransferProxyAdminOwnershipFunction } from './admin'; +import { ValidateImplementationOptions } from './utils/options'; +import { DeployBeaconProxyArtifact, DeployBeaconProxyFactory } from './proxy-deployment/deploy-beacon-proxy'; +import { DeployBeaconArtifact, DeployBeaconFactory } from './proxy-deployment/deploy-beacon'; +import { + DeployFunctionArtifact, + DeployFunctionFactory, + DeployFunctionFactoryNoArgs, +} from './proxy-deployment/deploy-proxy'; +import { UpgradeBeaconArtifact, UpgradeBeaconFactory } from './proxy-upgrade/upgrade-beacon'; +import { UpgradeProxyArtifact, UpgradeProxyFactory } from './proxy-upgrade/upgrade-proxy'; +import { DeployAdminFunction } from './proxy-deployment/deploy-proxy-admin'; + +export type UndefinedFunctionType = (...args: any[]) => any; + +export function makeUndefinedFunction(): UndefinedFunctionType { + return (..._: any[]) => { + throw new Error('This function is not implemented'); + }; +} export type ValidateImplementationFunction = ( ImplFactory: zk.ContractFactory, opts?: ValidateImplementationOptions, ) => Promise; +export interface PlatformHardhatUpgrades extends HardhatUpgrades { + deployContract: DeployContractFunctionOZ; + proposeUpgrade: any; + getDefaultApprovalProcess: GetDefaultApprovalProcessFunctionOZ; +} + +export interface HardhatUpgradesOZ { + deployProxy: DeployFunctionOZ; + upgradeProxy: UpgradeFunctionOZ; + validateImplementation: ValidateImplementationFunctionOZ; + validateUpgrade: ValidateUpgradeFunctionOZ; + deployImplementation: DeployImplementationFunctionOZ; + prepareUpgrade: PrepareUpgradeFunctionOZ; + deployBeacon: DeployBeaconFunctionOZ; + deployBeaconProxy: DeployBeaconProxyFunctionOZ; + upgradeBeacon: UpgradeBeaconFunctionOZ; + deployProxyAdmin: DeployAdminFunction; + forceImport: ForceImportFunctionOZ; + silenceWarnings: any; + admin: { + getInstance: GetInstanceFunction; + changeProxyAdmin: ChangeAdminFunction; + transferProxyAdminOwnership: TransferProxyAdminOwnershipFunction; + }; + erc1967: { + getAdminAddress: (proxyAdress: string) => Promise; + getImplementationAddress: (proxyAdress: string) => Promise; + getBeaconAddress: (proxyAdress: string) => Promise; + }; + beacon: { + getImplementationAddress: (beaconAddress: string) => Promise; + }; +} + export interface HardhatUpgrades { - deployProxy: DeployFunction; - upgradeProxy: UpgradeFunction; + deployProxy: DeployFunctionArtifact & DeployFunctionFactory & DeployFunctionFactoryNoArgs; + upgradeProxy: UpgradeProxyFactory & UpgradeProxyArtifact; validateImplementation: ValidateImplementationFunction; - deployBeacon: DeployBeaconFunction; - deployBeaconProxy: DeployBeaconProxyFunction; - upgradeBeacon: UpgradeBeaconFunction; + deployBeacon: DeployBeaconArtifact & DeployBeaconFactory; + deployBeaconProxy: DeployBeaconProxyFactory & DeployBeaconProxyArtifact; + upgradeBeacon: UpgradeBeaconFactory & UpgradeBeaconArtifact; deployProxyAdmin: DeployAdminFunction; admin: { getInstance: GetInstanceFunction; @@ -36,6 +94,19 @@ export interface HardhatUpgrades { estimateGasBeacon: EstimateProxyGasFunction; estimateGasBeaconProxy: EstimateBeaconGasFunction; }; + forceImport: UndefinedFunctionType; + silenceWarnings: UndefinedFunctionType; + validateUpgrade: UndefinedFunctionType; + deployImplementation: UndefinedFunctionType; + prepareUpgrade: UndefinedFunctionType; + beacon: { + getImplementationAddress: UndefinedFunctionType; + }; + erc1967: { + getAdminAddress: UndefinedFunctionType; + getImplementationAddress: UndefinedFunctionType; + getBeaconAddress: UndefinedFunctionType; + }; } export interface RunCompilerArgs { diff --git a/packages/hardhat-zksync-upgradable/src/plugin.ts b/packages/hardhat-zksync-upgradable/src/plugin.ts index 4cbc46652..ba7f43935 100644 --- a/packages/hardhat-zksync-upgradable/src/plugin.ts +++ b/packages/hardhat-zksync-upgradable/src/plugin.ts @@ -2,7 +2,7 @@ import { Deployer } from '@matterlabs/hardhat-zksync-deploy/dist/deployer'; import { getConstructorArguments } from '@matterlabs/hardhat-zksync-deploy/dist/utils'; import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { Contract } from 'zksync-ethers'; +import { Contract, ContractFactory } from 'zksync-ethers'; import { DeploymentType } from 'zksync-ethers/build/types'; import { getWallet } from './utils'; @@ -36,7 +36,9 @@ export async function deployBeacon( const deployer = new Deployer(hre, wallet); const contract = await deployer.loadArtifact(taskArgs.contractName); - const beacon = await hre.zkUpgrades.deployBeacon(wallet, contract, { + const factory = new ContractFactory(contract.abi, contract.bytecode, wallet); + + const beacon = await hre.zkUpgrades.deployBeacon(factory, [], { deploymentType: taskArgs.deploymentTypeImpl, salt: taskArgs.saltImpl, }); diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts index 84daf9d2d..340320518 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts @@ -16,105 +16,143 @@ import path from 'path'; import { ContractAddressOrInstance, getContractAddress, getInitializerData } from '../utils/utils-general'; import { DeployBeaconProxyOptions } from '../utils/options'; import { BEACON_PROXY_JSON } from '../constants'; -import { ZkSyncUpgradablePluginError } from '../errors'; import { Manifest } from '../core/manifest'; import { getUpgradableContracts } from '../utils'; import { deploy, DeployTransaction } from './deploy'; -export interface DeployBeaconProxyFunction { - ( - wallet: zk.Wallet, - beacon: ContractAddressOrInstance, - artifact: ZkSyncArtifact, - args?: unknown[], - opts?: DeployBeaconProxyOptions, - quiet?: boolean, - ): Promise; - ( - wallet: zk.Wallet, - beacon: ContractAddressOrInstance, - artifact: ZkSyncArtifact, - opts?: DeployBeaconProxyOptions, - ): Promise; -} - -export function makeDeployBeaconProxy(hre: HardhatRuntimeEnvironment): DeployBeaconProxyFunction { - return async function deployBeaconProxy( - wallet: zk.Wallet, - beacon: ContractAddressOrInstance, - artifact: ZkSyncArtifact, - args: unknown[] | DeployBeaconProxyOptions = [], - opts: DeployBeaconProxyOptions = {}, - quiet: boolean = false, - ) { - const attachTo = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet); - - if (!(attachTo instanceof zk.ContractFactory)) { - throw new ZkSyncUpgradablePluginError( - `attachTo must specify a contract factory\n` + - `Include the contract factory for the beacon's current implementation in the attachTo parameter`, - ); +export type DeployBeaconProxyFactory = ( + beacon: ContractAddressOrInstance, + factory: zk.ContractFactory, + args?: unknown[], + opts?: DeployBeaconProxyOptions, + quiet?: boolean, +) => Promise; + +export type DeployBeaconProxyArtifact = ( + wallet: zk.Wallet, + beacon: ContractAddressOrInstance, + artifact: ZkSyncArtifact, + args?: unknown[], + opts?: DeployBeaconProxyOptions, + quiet?: boolean, +) => Promise; + +export function makeDeployBeaconProxy( + hre: HardhatRuntimeEnvironment, +): DeployBeaconProxyFactory | DeployBeaconProxyArtifact { + return async function ( + ...args: Parameters + ): Promise { + const target = args[1]; + if (target instanceof zk.ContractFactory) { + return deployBeaconProxyFactory(hre, ...(args as Parameters)); + } else { + return deployBeaconProxyArtifact(hre, ...(args as Parameters)); } - if (!Array.isArray(args)) { - opts = args; - args = []; - } - - const manifest = await Manifest.forNetwork(wallet.provider); - - if (opts.kind !== undefined && opts.kind !== 'beacon') { - throw new DeployBeaconProxyKindError(opts.kind); - } - opts.kind = 'beacon'; + }; +} - const beaconAddress = getContractAddress(beacon); - if (!(await isBeacon(wallet.provider, beaconAddress))) { - throw new DeployBeaconProxyUnsupportedError(beaconAddress); - } +export function deployBeaconProxyArtifact( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + beacon: ContractAddressOrInstance, + artifact: ZkSyncArtifact, + args: unknown[] = [], + opts: DeployBeaconProxyOptions = {}, + quiet: boolean = false, +): Promise { + if (opts && opts.kind !== undefined && opts.kind !== 'beacon') { + throw new DeployBeaconProxyKindError(opts.kind); + } + const factory = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet); + opts = opts || {}; + opts.kind = 'beacon'; + return deployBeaconProxy(hre, beacon, factory, args, opts, wallet, quiet); +} - const data = getInitializerData(attachTo.interface, args, opts.initializer); - - if (await manifest.getAdmin()) { - if (!quiet) { - console.info( - chalk.yellow(`A proxy admin was previously deployed on this network`, [ - `This is not natively used with the current kind of proxy ('beacon').`, - `Changes to the admin will have no effect on this new proxy.`, - ]), - ); - } - } +export async function deployBeaconProxyFactory( + hre: HardhatRuntimeEnvironment, + beacon: ContractAddressOrInstance, + factory: zk.ContractFactory, + args: unknown[] = [], + opts: DeployBeaconProxyOptions = {}, + quiet: boolean = false, +): Promise { + if (opts && opts.kind !== undefined && opts.kind !== 'beacon') { + throw new DeployBeaconProxyKindError(opts.kind); + } + opts = opts || {}; + opts.kind = 'beacon'; + + const wallet = factory.signer && 'getAddress' in factory.signer ? (factory.signer as zk.Wallet) : undefined; + if (!wallet) throw new Error('Wallet not found. Please pass it in the arguments.'); + + return deployBeaconProxy(hre, beacon, factory, args, opts, wallet, quiet); +} - const beaconProxyPath = (await hre.artifacts.getArtifactPaths()).find((artifactPath) => - artifactPath.includes(path.sep + getUpgradableContracts().BeaconProxy + path.sep + BEACON_PROXY_JSON), - ); - assert(beaconProxyPath, 'Beacon proxy artifact not found'); - const beaconProxyContract = await import(beaconProxyPath); - - const beaconProxyFactory = new zk.ContractFactory( - beaconProxyContract.abi, - beaconProxyContract.bytecode, - wallet, - opts.deploymentType, - ); - - const proxyDeployment: Required = { - kind: opts.kind, - ...(await deploy(beaconProxyFactory, beaconAddress, data, { - customData: { - salt: opts.salt, - }, - })), - }; +async function deployBeaconProxy( + hre: HardhatRuntimeEnvironment, + beacon: ContractAddressOrInstance, + attachTo: zk.ContractFactory, + args: unknown[] = [], + opts: DeployBeaconProxyOptions = {}, + wallet: zk.Wallet, + quiet: boolean = false, +): Promise { + if (!Array.isArray(args)) { + opts = args; + args = []; + } + + const manifest = await Manifest.forNetwork(wallet.provider); + const beaconAddress = getContractAddress(beacon); + if (!(await isBeacon(wallet.provider, beaconAddress))) { + throw new DeployBeaconProxyUnsupportedError(beaconAddress); + } + + const data = getInitializerData(attachTo.interface, args, opts.initializer); + + if (await manifest.getAdmin()) { if (!quiet) { - console.info(chalk.green('Beacon proxy deployed at: ', proxyDeployment.address)); + console.info( + chalk.yellow(`A proxy admin was previously deployed on this network`, [ + `This is not natively used with the current kind of proxy ('beacon').`, + `Changes to the admin will have no effect on this new proxy.`, + ]), + ); } + } + + const beaconProxyPath = (await hre.artifacts.getArtifactPaths()).find((artifactPath) => + artifactPath.includes(path.sep + getUpgradableContracts().BeaconProxy + path.sep + BEACON_PROXY_JSON), + ); + assert(beaconProxyPath, 'Beacon proxy artifact not found'); + const beaconProxyContract = await import(beaconProxyPath); + + const beaconProxyFactory = new zk.ContractFactory( + beaconProxyContract.abi, + beaconProxyContract.bytecode, + wallet, + opts.deploymentType, + ); + + const proxyDeployment: Required = { + kind: opts.kind!, + ...(await deploy(beaconProxyFactory, beaconAddress, data, { + customData: { + salt: opts.salt, + }, + })), + }; - await manifest.addProxy(proxyDeployment); + if (!quiet) { + console.info(chalk.green('Beacon proxy deployed at: ', proxyDeployment.address)); + } - const inst = attachTo.attach(proxyDeployment.address); - // @ts-ignore Won't be readonly because inst was created through attach. - inst.deployTransaction = proxyDeployment.deployTransaction; - return inst; - }; + await manifest.addProxy(proxyDeployment); + + const inst = attachTo.attach(proxyDeployment.address) as zk.Contract; + // @ts-ignore Won't be readonly because inst was created through attach. + inst.deployTransaction = proxyDeployment.deployTransaction; + return inst.runner ? inst : (inst.connect(wallet) as zk.Contract); } diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon.ts index 2e6652139..813579b11 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon.ts @@ -9,54 +9,114 @@ import chalk from 'chalk'; import assert from 'assert'; import path from 'path'; import { UPGRADABLE_BEACON_JSON } from '../constants'; +import { extractFactoryDeps, getArtifactFromBytecode } from '../utils/utils-general'; +import { ZkSyncUpgradablePluginError } from '../errors'; import { DeployBeaconOptions } from '../utils/options'; -import { extractFactoryDeps } from '../utils/utils-general'; import { getUpgradableContracts } from '../utils'; import { deployBeaconImpl } from './deploy-impl'; import { deploy, DeployTransaction } from './deploy'; -export type DeployBeaconFunction = ( +export type DeployBeaconFactory = ( + factory: zk.ContractFactory, + args?: unknown[], + opts?: DeployBeaconOptions, + quiet?: boolean, +) => Promise; + +export type DeployBeaconArtifact = ( wallet: zk.Wallet, artifact: ZkSyncArtifact, + args?: unknown[], opts?: DeployBeaconOptions, quiet?: boolean, ) => Promise; -export function makeDeployBeacon(hre: HardhatRuntimeEnvironment): DeployBeaconFunction { - return async function deployBeacon( - wallet: zk.Wallet, - artifact: ZkSyncArtifact, - opts: DeployBeaconOptions = {}, - quiet: boolean = false, - ) { - const beaconImplFactory = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet, opts.deploymentType); - - opts.provider = wallet.provider; - opts.factoryDeps = await extractFactoryDeps(hre, artifact); - const { impl } = await deployBeaconImpl(hre, beaconImplFactory, opts); - if (!quiet) { - console.info(chalk.green('Beacon impl deployed at', impl)); +export function makeDeployBeacon(hre: HardhatRuntimeEnvironment): DeployBeaconFactory | DeployBeaconArtifact { + return async function (...args: Parameters): Promise { + const target = args[0]; + if (target instanceof zk.ContractFactory) { + return await deployBeaconFactory(hre, ...(args as Parameters)); + } else { + return deployBeaconArtifact(hre, ...(args as Parameters)); } + }; +} - const upgradableBeaconPath = (await hre.artifacts.getArtifactPaths()).find((x) => - x.includes(path.sep + getUpgradableContracts().UpgradeableBeacon + path.sep + UPGRADABLE_BEACON_JSON), - ); - assert(upgradableBeaconPath, 'Upgradable beacon artifact not found'); - const upgradeableBeaconContract = await import(upgradableBeaconPath); - - const upgradeableBeaconFactory = new zk.ContractFactory( - upgradeableBeaconContract.abi, - upgradeableBeaconContract.bytecode, - wallet, - ); - const beaconDeployment: Required = await deploy(upgradeableBeaconFactory, impl); - if (!quiet) { - console.info(chalk.green('Beacon deployed at: ', beaconDeployment.address)); - } +export async function deployBeaconFactory( + hre: HardhatRuntimeEnvironment, + factory: zk.ContractFactory, + args?: unknown[], + opts?: DeployBeaconOptions, + quiet?: boolean, +): Promise { + if (!Array.isArray(args)) { + opts = args; + args = []; + } - const beaconContract = upgradeableBeaconFactory.attach(beaconDeployment.address); - // @ts-ignore Won't be readonly because beaconContract was created through attach. - beaconContract.deployTransaction = beaconDeployment.deployTransaction; - return beaconContract; - }; + const wallet = factory.signer && 'getAddress' in factory.signer ? (factory.signer as zk.Wallet) : undefined; + if (!wallet) { + throw new ZkSyncUpgradablePluginError('Wallet is required for deployment'); + } + + opts = opts || {}; + opts.provider = wallet?.provider; + opts.factoryDeps = await extractFactoryDeps(hre, await getArtifactFromBytecode(hre, factory.bytecode)); + + return deployProxyBeacon(hre, factory, wallet, undefined, opts, quiet); +} + +export async function deployBeaconArtifact( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + artifact: ZkSyncArtifact, + args?: unknown[], + opts?: DeployBeaconOptions, + quiet?: boolean, +): Promise { + const factory = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet); + opts = opts || {}; + opts.provider = wallet.provider; + opts.factoryDeps = await extractFactoryDeps(hre, artifact as ZkSyncArtifact); + return deployProxyBeacon(hre, factory, wallet, args, opts, quiet); +} + +async function deployProxyBeacon( + hre: HardhatRuntimeEnvironment, + factory: zk.ContractFactory, + wallet: zk.Wallet, + args: unknown[] | DeployBeaconOptions = [], + opts: DeployBeaconOptions = {}, + quiet: boolean = false, +): Promise { + if (!Array.isArray(args)) { + opts = args; + args = []; + } + + const { impl } = await deployBeaconImpl(hre, factory, opts); + if (!quiet) { + console.info(chalk.green('Beacon impl deployed at', impl)); + } + + const upgradableBeaconPath = (await hre.artifacts.getArtifactPaths()).find((x) => + x.includes(path.sep + getUpgradableContracts().UpgradeableBeacon + path.sep + UPGRADABLE_BEACON_JSON), + ); + assert(upgradableBeaconPath, 'Upgradable beacon artifact not found'); + const upgradeableBeaconContract = await import(upgradableBeaconPath); + + const upgradeableBeaconFactory = new zk.ContractFactory( + upgradeableBeaconContract.abi, + upgradeableBeaconContract.bytecode, + wallet, + ); + const beaconDeployment: Required = await deploy(upgradeableBeaconFactory, impl); + if (!quiet) { + console.info(chalk.green('Beacon deployed at: ', beaconDeployment.address)); + } + + const beaconContract = upgradeableBeaconFactory.attach(beaconDeployment.address); + // @ts-ignore Won't be readonly because beaconContract was created through attach. + beaconContract.deployTransaction = beaconDeployment.deployTransaction; + return beaconContract.runner ? beaconContract : (beaconContract.connect(wallet) as zk.Contract); } diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts index 84dbd9220..653bf7f38 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts @@ -7,16 +7,29 @@ import { BeaconProxyUnsupportedError } from '@openzeppelin/upgrades-core'; import { ZkSyncArtifact } from '@matterlabs/hardhat-zksync-deploy/src/types'; import assert from 'assert'; -import { extractFactoryDeps, getInitializerData } from '../utils/utils-general'; +import { extractFactoryDeps, getArtifactFromBytecode, getInitializerData } from '../utils/utils-general'; import { ERC1967_PROXY_JSON, TUP_JSON } from '../constants'; import { Manifest, ProxyDeployment } from '../core/manifest'; -import { DeployProxyOptions } from '../utils/options'; import { ZkSyncUpgradablePluginError } from '../errors'; +import { DeployProxyOptions } from '../utils/options'; import { getUpgradableContracts } from '../utils'; import { deployProxyImpl } from './deploy-impl'; import { DeployTransaction, deploy } from './deploy'; -export type DeployFunction = ( +export type DeployFunctionFactoryNoArgs = ( + factory: zk.ContractFactory, + opts?: DeployProxyOptions, + quiet?: boolean, +) => Promise; + +export type DeployFunctionFactory = ( + factory: zk.ContractFactory, + args?: unknown[], + opts?: DeployProxyOptions, + quiet?: boolean, +) => Promise; + +export type DeployFunctionArtifact = ( wallet: zk.Wallet, artifact: ZkSyncArtifact, args?: unknown[], @@ -24,111 +37,179 @@ export type DeployFunction = ( quiet?: boolean, ) => Promise; -export function makeDeployProxy(hre: HardhatRuntimeEnvironment): DeployFunction { - return async function deployProxy( - wallet, - artifact, - args: unknown[] | DeployProxyOptions = [], - opts: DeployProxyOptions = {}, - quiet: boolean = false, - ) { - if (!Array.isArray(args)) { - opts = args; - args = []; +export function makeDeployProxy( + hre: HardhatRuntimeEnvironment, +): DeployFunctionFactory | DeployFunctionArtifact | DeployFunctionFactoryNoArgs { + return async function (...args: Parameters): Promise { + const target = args[0]; + if (target instanceof zk.ContractFactory) { + const targetArgs = args[1]; + if (targetArgs && 'initializer' in targetArgs) { + return await deployProxyFactoryNoArgs(hre, ...(args as Parameters)); + } + return await deployProxyFactory(hre, ...(args as Parameters)); + } else { + return deployProxyArtifact(hre, ...(args as Parameters)); } - opts.provider = wallet.provider; - opts.factoryDeps = await extractFactoryDeps(hre, artifact); - - const manifest = await Manifest.forNetwork(wallet.provider); + }; +} - const factory = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet, opts.deploymentTypeImpl); +export async function deployProxyFactoryNoArgs( + hre: HardhatRuntimeEnvironment, + factory: zk.ContractFactory, + opts?: DeployProxyOptions, + quiet?: boolean, +): Promise { + const wallet = factory.signer && 'getAddress' in factory.signer ? (factory.signer as zk.Wallet) : undefined; + if (!wallet) { + throw new ZkSyncUpgradablePluginError('Wallet is required for deployment'); + } + opts = opts || {}; + opts.provider = wallet?.provider; + opts.factoryDeps = await extractFactoryDeps(hre, await getArtifactFromBytecode(hre, factory.bytecode)); + + return deployProxy(hre, factory, wallet, undefined, opts, quiet); +} - const { impl, kind } = await deployProxyImpl(hre, factory, opts); - if (!quiet) { - console.info(chalk.green(`Implementation contract was deployed to ${impl}`)); - } +export async function deployProxyFactory( + hre: HardhatRuntimeEnvironment, + factory: zk.ContractFactory, + args?: unknown[], + opts?: DeployProxyOptions, + quiet?: boolean, +): Promise { + if (!Array.isArray(args)) { + opts = args; + args = []; + } + + const wallet = factory.signer && 'getAddress' in factory.signer ? (factory.signer as zk.Wallet) : undefined; + if (!wallet) { + throw new ZkSyncUpgradablePluginError('Wallet is required for deployment'); + } + opts = opts || {}; + opts.provider = wallet?.provider; + opts.factoryDeps = await extractFactoryDeps(hre, await getArtifactFromBytecode(hre, factory.bytecode)); + + return deployProxy(hre, factory, wallet, args, opts, quiet); +} - const contractInterface = factory.interface; - const data = getInitializerData(contractInterface, args, opts.initializer); - - const customDataProxy = { - customData: { - salt: opts.saltProxy, - }, - }; - - if (kind === 'uups') { - if (await manifest.getAdmin()) { - if (!quiet) { - console.info( - chalk.yellow( - `A proxy admin was previously deployed on this network\nThis is not natively used with the current kind of proxy ('uups')\nChanges to the admin will have no effect on this new proxy`, - ), - ); - } - } - } +export async function deployProxyArtifact( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + artifact: ZkSyncArtifact, + args?: unknown[], + opts?: DeployProxyOptions, + quiet?: boolean, +): Promise { + const factory = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet); + opts = opts || {}; + opts.provider = wallet.provider; + opts.factoryDeps = await extractFactoryDeps(hre, artifact as ZkSyncArtifact); + return deployProxy(hre, factory, wallet, args, opts, quiet); +} - let proxyDeployment: Required; - switch (kind) { - case 'beacon': { - throw new BeaconProxyUnsupportedError(); - } +async function deployProxy( + hre: HardhatRuntimeEnvironment, + factory: zk.ContractFactory, + wallet: zk.Wallet, + args: unknown[] | DeployProxyOptions = [], + opts: DeployProxyOptions = {}, + quiet: boolean = false, +): Promise { + if (!Array.isArray(args)) { + opts = args; + args = []; + } + + const manifest = await Manifest.forNetwork(wallet.provider); + + const { impl, kind } = await deployProxyImpl(hre, factory, opts); + if (!quiet) { + console.info(chalk.green(`Implementation contract was deployed to ${impl}`)); + } + + const data = getInitializerData(factory.interface, args, opts.initializer); + + const customDataProxy = { + customData: { + salt: opts.saltProxy, + }, + }; - case 'uups': { - const ERC1967ProxyPath = (await hre.artifacts.getArtifactPaths()).find((x) => - x.includes(path.sep + getUpgradableContracts().ERC1967Proxy + path.sep + ERC1967_PROXY_JSON), + if (kind === 'uups') { + if (await manifest.getAdmin()) { + if (!quiet) { + console.info( + chalk.yellow( + `A proxy admin was previously deployed on this network\nThis is not natively used with the current kind of proxy ('uups')\nChanges to the admin will have no effect on this new proxy`, + ), ); - assert(ERC1967ProxyPath, 'ERC1967Proxy artifact not found'); - const proxyContract = await import(ERC1967ProxyPath); - const proxyFactory = new zk.ContractFactory( - proxyContract.abi, - proxyContract.bytecode, - wallet, - opts.deploymentTypeProxy, - ); - proxyDeployment = { kind, ...(await deploy(proxyFactory, impl, data, customDataProxy)) }; - if (!quiet) { - console.info(chalk.green(`UUPS proxy was deployed to ${proxyDeployment.address}`)); - } - break; } + } + } - case 'transparent': { - const adminAddress = await hre.zkUpgrades.deployProxyAdmin(wallet, {}); - if (!quiet) { - console.info(chalk.green(`Admin was deployed to ${adminAddress}`)); - } + let proxyDeployment: Required; + switch (kind) { + case 'beacon': { + throw new BeaconProxyUnsupportedError(); + } - const TUPPath = (await hre.artifacts.getArtifactPaths()).find((x) => - x.includes(path.sep + getUpgradableContracts().TransparentUpgradeableProxy + path.sep + TUP_JSON), - ); - assert(TUPPath, 'TUP artifact not found'); - const TUPContract = await import(TUPPath); - - const TUPFactory = new zk.ContractFactory( - TUPContract.abi, - TUPContract.bytecode, - wallet, - opts.deploymentTypeProxy, - ); - proxyDeployment = { kind, ...(await deploy(TUPFactory, impl, adminAddress, data, customDataProxy)) }; - if (!quiet) { - console.info(chalk.green(`Transparent proxy was deployed to ${proxyDeployment.address}`)); - } + case 'uups': { + const ERC1967ProxyPath = (await hre.artifacts.getArtifactPaths()).find((x) => + x.includes(path.sep + getUpgradableContracts().ERC1967Proxy + path.sep + ERC1967_PROXY_JSON), + ); + assert(ERC1967ProxyPath, 'ERC1967Proxy artifact not found'); + const proxyContract = await import(ERC1967ProxyPath); + const proxyFactory = new zk.ContractFactory( + proxyContract.abi, + proxyContract.bytecode, + wallet, + opts.deploymentTypeProxy, + ); + proxyDeployment = { kind, ...(await deploy(proxyFactory, impl, data, customDataProxy)) }; + + if (!quiet) { + console.info(chalk.green(`UUPS proxy was deployed to ${proxyDeployment.address}`)); + } + break; + } - break; + case 'transparent': { + const adminAddress = await hre.zkUpgrades.deployProxyAdmin(wallet, {}); + if (!quiet) { + console.info(chalk.green(`Admin was deployed to ${adminAddress}`)); } - default: { - throw new ZkSyncUpgradablePluginError(`Unknown proxy kind: ${kind}`); + const TUPPath = (await hre.artifacts.getArtifactPaths()).find((x) => + x.includes(path.sep + getUpgradableContracts().TransparentUpgradeableProxy + path.sep + TUP_JSON), + ); + assert(TUPPath, 'TUP artifact not found'); + const TUPContract = await import(TUPPath); + + const TUPFactory = new zk.ContractFactory( + TUPContract.abi, + TUPContract.bytecode, + wallet, + opts.deploymentTypeProxy, + ); + proxyDeployment = { kind, ...(await deploy(TUPFactory, impl, adminAddress, data, customDataProxy)) }; + + if (!quiet) { + console.info(chalk.green(`Transparent proxy was deployed to ${proxyDeployment.address}`)); } + + break; } - await manifest.addProxy(proxyDeployment); - const inst = factory.attach(proxyDeployment.address); - // @ts-ignore Won't be readonly because inst was created through attach. - inst.deployTransaction = proxyDeployment.deployTransaction; - return inst; - }; + default: { + throw new ZkSyncUpgradablePluginError(`Unknown proxy kind: ${kind}`); + } + } + + await manifest.addProxy(proxyDeployment); + const inst = factory.attach(proxyDeployment.address); + // @ts-ignore Won't be readonly because inst was created through attach. + inst.deployTransaction = proxyDeployment.deployTransaction; + return inst.runner ? (inst as zk.Contract) : (inst.connect(wallet) as zk.Contract); } diff --git a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts index 9a39bf0fd..73f24179d 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts @@ -5,13 +5,26 @@ import { ZkSyncArtifact } from '@matterlabs/hardhat-zksync-deploy/src/types'; import chalk from 'chalk'; import assert from 'assert'; -import { ContractAddressOrInstance, extractFactoryDeps, getContractAddress } from '../utils/utils-general'; +import { + ContractAddressOrInstance, + extractFactoryDeps, + getArtifactFromBytecode, + getContractAddress, +} from '../utils/utils-general'; import { UpgradeBeaconOptions } from '../utils/options'; import { deployBeaconImpl } from '../proxy-deployment/deploy-impl'; import { UPGRADABLE_BEACON_JSON } from '../constants'; +import { ZkSyncUpgradablePluginError } from '../errors'; import { getUpgradableContracts } from '../utils'; -export type UpgradeBeaconFunction = ( +export type UpgradeBeaconFactory = ( + beacon: ContractAddressOrInstance, + factory: zk.ContractFactory, + opts?: UpgradeBeaconOptions, + quiet?: boolean, +) => Promise; + +export type UpgradeBeaconArtifact = ( wallet: zk.Wallet, beacon: ContractAddressOrInstance, artifact: ZkSyncArtifact, @@ -19,47 +32,82 @@ export type UpgradeBeaconFunction = ( quiet?: boolean, ) => Promise; -export function makeUpgradeBeacon(hre: HardhatRuntimeEnvironment): UpgradeBeaconFunction { - return async function upgradeBeacon( - wallet, - beaconImplementation, - newImplementationArtifact, - opts: UpgradeBeaconOptions = {}, - quiet: boolean = false, - ) { - const factory = new zk.ContractFactory( - newImplementationArtifact.abi, - newImplementationArtifact.bytecode, - wallet, - opts.deploymentType, - ); +export async function upgradeBeaconFactory( + hre: HardhatRuntimeEnvironment, + beacon: ContractAddressOrInstance, + factory: zk.ContractFactory, + opts?: UpgradeBeaconOptions, + quiet?: boolean, +): Promise { + const wallet = factory.signer && 'getAddress' in factory.signer ? (factory.signer as zk.Wallet) : undefined; + if (!wallet) { + throw new ZkSyncUpgradablePluginError('Wallet is required for upgrade.'); + } - opts.provider = wallet.provider; - opts.factoryDeps = await extractFactoryDeps(hre, newImplementationArtifact); + opts = opts || {}; + opts.provider = wallet.provider; + opts.factoryDeps = await extractFactoryDeps(hre, await getArtifactFromBytecode(hre, factory.bytecode)); - const beaconImplementationAddress = getContractAddress(beaconImplementation); - const { impl: nextImpl } = await deployBeaconImpl(hre, factory, opts, beaconImplementationAddress); - if (!quiet) { - console.info(chalk.green('New beacon impl deployed at', nextImpl)); - } + return upgradeBeacon(hre, wallet, beacon, factory, opts, quiet); +} - const upgradableBeaconPath = (await hre.artifacts.getArtifactPaths()).find((x) => - x.includes(path.sep + getUpgradableContracts().UpgradeableBeacon + path.sep + UPGRADABLE_BEACON_JSON), - ); - assert(upgradableBeaconPath, 'Upgradable beacon artifact not found'); - const upgradeableBeaconContract = await import(upgradableBeaconPath); +export async function upgradeBeaconArtifact( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + beacon: ContractAddressOrInstance, + artifact: ZkSyncArtifact, + opts?: UpgradeBeaconOptions, + quiet?: boolean, +): Promise { + const factory = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet); + opts = opts || {}; + opts.provider = wallet.provider; + opts.factoryDeps = await extractFactoryDeps(hre, artifact as ZkSyncArtifact); + + return upgradeBeacon(hre, wallet, beacon, factory, opts, quiet); +} + +async function upgradeBeacon( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + beaconImplementation: ContractAddressOrInstance, + newImplementationFactory: zk.ContractFactory, + opts: UpgradeBeaconOptions = {}, + quiet: boolean = false, +) { + const beaconImplementationAddress = getContractAddress(beaconImplementation); + const { impl: nextImpl } = await deployBeaconImpl(hre, newImplementationFactory, opts, beaconImplementationAddress); + if (!quiet) { + console.info(chalk.green('New beacon impl deployed at', nextImpl)); + } + + const upgradableBeaconPath = (await hre.artifacts.getArtifactPaths()).find((x) => + x.includes(path.sep + getUpgradableContracts().UpgradeableBeacon + path.sep + UPGRADABLE_BEACON_JSON), + ); + assert(upgradableBeaconPath, 'Upgradable beacon artifact not found'); + const upgradeableBeaconContract = await import(upgradableBeaconPath); + + const upgradeableBeaconFactory = new zk.ContractFactory( + upgradeableBeaconContract.abi, + upgradeableBeaconContract.bytecode, + wallet, + ); - const upgradeableBeaconFactory = new zk.ContractFactory( - upgradeableBeaconContract.abi, - upgradeableBeaconContract.bytecode, - wallet, - ); + const beaconContract = upgradeableBeaconFactory.attach(beaconImplementationAddress); + const upgradeTx = await beaconContract.upgradeTo(nextImpl); - const beaconContract = upgradeableBeaconFactory.attach(beaconImplementationAddress); - const upgradeTx = await beaconContract.upgradeTo(nextImpl); + // @ts-ignore Won't be readonly because beaconContract was created through attach. + beaconContract.deployTransaction = upgradeTx; + return beaconContract; +} - // @ts-ignore Won't be readonly because beaconContract was created through attach. - beaconContract.deployTransaction = upgradeTx; - return beaconContract; +export function makeUpgradeBeacon(hre: HardhatRuntimeEnvironment): UpgradeBeaconArtifact | UpgradeBeaconFactory { + return async function (...args: Parameters): Promise { + const target = args[0]; + if (target instanceof zk.ContractFactory) { + return await upgradeBeaconFactory(hre, ...(args as Parameters)); + } else { + return upgradeBeaconArtifact(hre, ...(args as Parameters)); + } }; } diff --git a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts index 0454f356f..4be6356ab 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts @@ -10,13 +10,21 @@ import chalk from 'chalk'; import assert from 'assert'; import { ContractAddressOrInstance } from '../interfaces'; import { UpgradeProxyOptions } from '../utils/options'; -import { extractFactoryDeps, getContractAddress } from '../utils/utils-general'; +import { extractFactoryDeps, getArtifactFromBytecode, getContractAddress } from '../utils/utils-general'; import { deployProxyImpl } from '../proxy-deployment/deploy-impl'; import { Manifest } from '../core/manifest'; import { ITUP_JSON, PROXY_ADMIN_JSON } from '../constants'; +import { ZkSyncUpgradablePluginError } from '../errors'; import { getUpgradableContracts } from '../utils'; -export type UpgradeFunction = ( +export type UpgradeProxyFactory = ( + proxy: ContractAddressOrInstance, + factory: zk.ContractFactory, + opts?: UpgradeProxyOptions, + quiet?: boolean, +) => Promise; + +export type UpgradeProxyArtifact = ( wallet: zk.Wallet, proxy: ContractAddressOrInstance, artifact: ZkSyncArtifact, @@ -24,88 +32,119 @@ export type UpgradeFunction = ( quiet?: boolean, ) => Promise; -export function makeUpgradeProxy(hre: HardhatRuntimeEnvironment): UpgradeFunction { - return async function upgradeProxy( - wallet, - proxy, - newImplementationArtifact, - opts: UpgradeProxyOptions = {}, - quiet: boolean = false, - ) { - const proxyAddress = getContractAddress(proxy); - opts.provider = wallet.provider; - opts.factoryDeps = await extractFactoryDeps(hre, newImplementationArtifact); - - const newImplementationFactory = new zk.ContractFactory( - newImplementationArtifact.abi, - newImplementationArtifact.bytecode, - wallet, - opts.deploymentType, - ); - const { impl: nextImpl } = await deployProxyImpl(hre, newImplementationFactory, opts, proxyAddress); - - const upgradeTo = await getUpgrader(proxyAddress, wallet); - const call = encodeCall(newImplementationFactory, opts.call); - const upgradeTx = await upgradeTo(nextImpl, call); +type Upgrader = (nextImpl: string, call?: string) => Promise; - if (!quiet) { - console.info(chalk.green(`Contract successfully upgraded to ${nextImpl} with tx ${upgradeTx.hash}`)); +export function makeUpgradeProxy(hre: HardhatRuntimeEnvironment): UpgradeProxyFactory | UpgradeProxyArtifact { + return async function (...args: Parameters): Promise { + const target = args[0]; + if (target instanceof zk.ContractFactory || args[1] instanceof zk.ContractFactory) { + return await upgradeProxyFactory(hre, ...(args as Parameters)); + } else { + return upgradeProxyArtifact(hre, ...(args as Parameters)); } - - const inst = newImplementationFactory.attach(proxyAddress); - // @ts-ignore Won't be readonly because inst was created through attach. - inst.deployTransaction = upgradeTx; - return inst; }; +} + +export async function upgradeProxyFactory( + hre: HardhatRuntimeEnvironment, + proxy: ContractAddressOrInstance, + factory: zk.ContractFactory, + opts?: UpgradeProxyOptions, + quiet?: boolean, +): Promise { + const wallet = factory.signer && 'getAddress' in factory.signer ? (factory.signer as zk.Wallet) : undefined; + if (!wallet) { + throw new ZkSyncUpgradablePluginError('Wallet is required for upgrade.'); + } + + opts = opts || {}; + opts.provider = wallet.provider; + opts.factoryDeps = await extractFactoryDeps(hre, await getArtifactFromBytecode(hre, factory.bytecode)); + return upgradeProxy(hre, wallet, proxy, factory, opts, quiet); +} + +export async function upgradeProxyArtifact( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + proxy: ContractAddressOrInstance, + artifact: ZkSyncArtifact, + opts?: UpgradeProxyOptions, + quiet?: boolean, +): Promise { + const factory = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet); + opts = opts || {}; + opts.provider = wallet.provider; + opts.factoryDeps = await extractFactoryDeps(hre, artifact as ZkSyncArtifact); + return upgradeProxy(hre, wallet, proxy, factory, opts, quiet); +} - type Upgrader = (nextImpl: string, call?: string) => Promise; +async function upgradeProxy( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + proxy: ContractAddressOrInstance, + factory: zk.ContractFactory, + opts: UpgradeProxyOptions = {}, + quiet: boolean = false, +) { + const proxyAddress = getContractAddress(proxy); - async function getUpgrader(proxyAddress: string, wallet: zk.Wallet): Promise { - const provider = wallet.provider as zk.Provider; + const { impl: nextImpl } = await deployProxyImpl(hre, factory, opts, proxyAddress); - const adminAddress = await getAdminAddress(provider, proxyAddress); - const adminBytecode = await getCode(provider, adminAddress); + const upgradeTo = await getUpgrader(hre, proxyAddress, wallet); + const call = encodeCall(factory, opts.call); + const upgradeTx = await upgradeTo(nextImpl, call); - if (isEmptySlot(adminAddress) || adminBytecode === '0x') { - const TUPPath = (await hre.artifacts.getArtifactPaths()).find((x) => - x.includes(path.sep + getUpgradableContracts().TransparentUpgradeableProxy + path.sep + ITUP_JSON), - ); - assert(TUPPath, 'Transparent upgradeable proxy artifact not found'); - const transparentUpgradeableProxyContract = await import(TUPPath); + if (!quiet) { + console.info(chalk.green(`Contract successfully upgraded to ${nextImpl} with tx ${upgradeTx.hash}`)); + } - const transparentUpgradeableProxyFactory = new zk.ContractFactory( - transparentUpgradeableProxyContract.abi, - transparentUpgradeableProxyContract.bytecode, - wallet, - ); - const proxy = transparentUpgradeableProxyFactory.attach(proxyAddress); + const inst = factory.attach(proxyAddress); + // @ts-ignore Won't be readonly because inst was created through attach. + inst.deployTransaction = upgradeTx; + return inst as zk.Contract; +} - return (nextImpl, call) => (call ? proxy.upgradeToAndCall(nextImpl, call) : proxy.upgradeTo(nextImpl)); - } else { - const manifest = await Manifest.forNetwork(provider); +async function getUpgrader(hre: HardhatRuntimeEnvironment, proxyAddress: string, wallet: zk.Wallet): Promise { + const provider = wallet.provider as zk.Provider; - const proxyAdminPath = (await hre.artifacts.getArtifactPaths()).find((x) => - x.includes(path.sep + getUpgradableContracts().ProxyAdmin + path.sep + PROXY_ADMIN_JSON), - ); - assert(proxyAdminPath, 'Proxy admin artifact not found'); - const proxyAdminContract = await import(proxyAdminPath); + const adminAddress = await getAdminAddress(provider, proxyAddress); + const adminBytecode = await getCode(provider, adminAddress); - const proxyAdminFactory = new zk.ContractFactory( - proxyAdminContract.abi, - proxyAdminContract.bytecode, - wallet, - ); + if (isEmptySlot(adminAddress) || adminBytecode === '0x') { + const TUPPath = (await hre.artifacts.getArtifactPaths()).find((x) => + x.includes(path.sep + getUpgradableContracts().TransparentUpgradeableProxy + path.sep + ITUP_JSON), + ); + assert(TUPPath, 'Transparent upgradeable proxy artifact not found'); + const transparentUpgradeableProxyContract = await import(TUPPath); - const admin = proxyAdminFactory.attach(adminAddress); - const manifestAdmin = await manifest.getAdmin(); + const transparentUpgradeableProxyFactory = new zk.ContractFactory( + transparentUpgradeableProxyContract.abi, + transparentUpgradeableProxyContract.bytecode, + wallet, + ); + const proxy = transparentUpgradeableProxyFactory.attach(proxyAddress); - if (admin.address !== manifestAdmin?.address) { - throw new Error('Proxy admin is not the one registered in the network manifest'); - } + return (nextImpl, call) => (call ? proxy.upgradeToAndCall(nextImpl, call) : proxy.upgradeTo(nextImpl)); + } else { + const manifest = await Manifest.forNetwork(provider); - return (nextImpl, call) => - call ? admin.upgradeAndCall(proxyAddress, nextImpl, call) : admin.upgrade(proxyAddress, nextImpl); + const proxyAdminPath = (await hre.artifacts.getArtifactPaths()).find((x) => + x.includes(path.sep + getUpgradableContracts().ProxyAdmin + path.sep + PROXY_ADMIN_JSON), + ); + assert(proxyAdminPath, 'Proxy admin artifact not found'); + const proxyAdminContract = await import(proxyAdminPath); + + const proxyAdminFactory = new zk.ContractFactory(proxyAdminContract.abi, proxyAdminContract.bytecode, wallet); + + const admin = proxyAdminFactory.attach(adminAddress); + const manifestAdmin = await manifest.getAdmin(); + + if (admin.address !== manifestAdmin?.address) { + throw new Error('Proxy admin is not the one registered in the network manifest'); } + + return (nextImpl, call) => + call ? admin.upgradeAndCall(proxyAddress, nextImpl, call) : admin.upgrade(proxyAddress, nextImpl); } } diff --git a/packages/hardhat-zksync-upgradable/src/type-extensions.ts b/packages/hardhat-zksync-upgradable/src/type-extensions.ts index 4af6e7134..1ca45611f 100644 --- a/packages/hardhat-zksync-upgradable/src/type-extensions.ts +++ b/packages/hardhat-zksync-upgradable/src/type-extensions.ts @@ -1,8 +1,10 @@ import 'hardhat/types/runtime'; -import { HardhatUpgrades } from './interfaces'; +import { HardhatUpgrades, HardhatUpgradesOZ, PlatformHardhatUpgrades } from './interfaces'; declare module 'hardhat/types/runtime' { export interface HardhatRuntimeEnvironment { zkUpgrades: HardhatUpgrades; + upgrades: HardhatUpgrades & HardhatUpgradesOZ; + platform: PlatformHardhatUpgrades; } } diff --git a/packages/hardhat-zksync-upgradable/src/utils.ts b/packages/hardhat-zksync-upgradable/src/utils.ts index 0daec7455..dbd675acd 100644 --- a/packages/hardhat-zksync-upgradable/src/utils.ts +++ b/packages/hardhat-zksync-upgradable/src/utils.ts @@ -19,8 +19,11 @@ export async function getWallet(hre: HardhatRuntimeEnvironment, privateKeyOrInde return wallet; } -export function checkOpenzeppelinVersions(wrappedFunction: (...args: any) => T): (...args: any) => T { - return function (...args: any): T { +export function wrapMakeFunction( + hre: HardhatRuntimeEnvironment, + wrappedFunction: (...args: any) => T, +): (...args: any) => Promise { + return async function (...args: any): Promise { try { if (!isOpenzeppelinContractsVersionValid()) { throw new Error(OZ_CONTRACTS_VERISION_INCOMPATIBLE_ERROR); @@ -28,6 +31,20 @@ export function checkOpenzeppelinVersions(wrappedFunction: (...args: any) => } catch (e: any) { console.warn(chalk.yellow(e.message)); } + + const upgradableContracts = getUpgradableContracts(); + // @ts-ignore + hre.config.zksolc.settings.overrideContractsToCompile = [ + upgradableContracts.ProxyAdmin, + upgradableContracts.TransparentUpgradeableProxy, + upgradableContracts.BeaconProxy, + upgradableContracts.UpgradeableBeacon, + upgradableContracts.ERC1967Proxy, + ]; + await hre.run('compile', { quiet: true }); + // @ts-ignore + hre.config.zksolc.settings.overrideContractsToCompile = undefined; + return wrappedFunction(...args); }; } @@ -52,3 +69,17 @@ export function getUpgradableContracts() { return UPGRADEABLE_CONTRACTS_FROM_ALIAS; } + +export function tryRequire(id: string, resolveOnly?: boolean) { + try { + if (resolveOnly) { + require.resolve(id); + } else { + require(id); + } + return true; + } catch (e: any) { + // do nothing + } + return false; +} diff --git a/packages/hardhat-zksync-upgradable/src/utils/utils-general.ts b/packages/hardhat-zksync-upgradable/src/utils/utils-general.ts index 108669ba7..1fa30a256 100644 --- a/packages/hardhat-zksync-upgradable/src/utils/utils-general.ts +++ b/packages/hardhat-zksync-upgradable/src/utils/utils-general.ts @@ -181,3 +181,17 @@ export async function extractFactoryDepsRecursive( return factoryDeps; } + +export async function getArtifactFromBytecode( + hre: HardhatRuntimeEnvironment, + bytecode: string, +): Promise { + const names = await hre.artifacts.getAllFullyQualifiedNames(); + for (const name of names) { + const artifact = await hre.artifacts.readArtifact(name); + if (artifact.bytecode === bytecode) { + return artifact as ZkSyncArtifact; + } + } + throw new ZkSyncUpgradablePluginError('Artifact for provided bytecode is not found.'); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7483ee906..369841661 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -222,6 +222,76 @@ importers: specifier: ^5.3.0 version: 5.4.5 + examples/upgradable-example-l1: + dependencies: + '@matterlabs/hardhat-zksync-deploy': + specifier: workspace:^ + version: link:../../packages/hardhat-zksync-deploy + '@matterlabs/hardhat-zksync-solc': + specifier: ^1.2.0 + version: 1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@matterlabs/hardhat-zksync-upgradable': + specifier: workspace:^ + version: link:../../packages/hardhat-zksync-upgradable + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@openzeppelin/contracts-upgradeable': + specifier: ^4.9.2 + version: 4.9.6 + chalk: + specifier: ^4.1.2 + version: 4.1.2 + hardhat: + specifier: 2.14.0 + version: 2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10) + zksync: + specifier: ^0.13.1 + version: 0.13.1(@ethersproject/logger@5.7.0)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + zksync-ethers: + specifier: ^5.8.0 + version: 5.8.0(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + devDependencies: + '@openzeppelin/contracts': + specifier: ^4.9.2 + version: 4.9.6 + '@types/node': + specifier: ^18.11.17 + version: 18.19.36 + '@typescript-eslint/eslint-plugin': + specifier: ^7.12.0 + version: 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^7.12.0 + version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) + eslint: + specifier: ^8.56.0 + version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + eslint-plugin-import: + specifier: ^2.29.1 + version: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-no-only-tests: + specifier: ^3.1.0 + version: 3.1.0 + eslint-plugin-prettier: + specifier: ^5.0.1 + version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.0) + prettier: + specifier: ^3.3.0 + version: 3.3.0 + rimraf: + specifier: ^5.0.7 + version: 5.0.7 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.19.36)(typescript@5.4.5) + typescript: + specifier: ^5.3.0 + version: 5.4.5 + packages/hardhat-zksync: dependencies: '@matterlabs/hardhat-zksync-deploy': @@ -439,6 +509,9 @@ importers: '@openzeppelin/contracts-hardhat-zksync-upgradable': specifier: npm:@openzeppelin/contracts@^4.9.2 version: '@openzeppelin/contracts@4.9.6' + '@openzeppelin/hardhat-upgrades': + specifier: ^1.28.0 + version: 1.28.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)))(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@openzeppelin/upgrades-core': specifier: ~1.29.0 version: 1.29.0 @@ -530,6 +603,19 @@ importers: packages: + '@aws-crypto/sha256-js@1.2.2': + resolution: {integrity: sha512-Nr1QJIbW/afYYGzYvrF70LtaHrIRtd4TNAglX8BvlfxJLZ45SAmueIKYl5tWoNBPzp65ymXGFK0Bb1vZUpuc9g==, tarball: https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz} + + '@aws-crypto/util@1.2.2': + resolution: {integrity: sha512-H8PjG5WJ4wz0UXAFXeJjWCW1vkvIJ3qUUD+rGRwJ2/hj+xT58Qle2MTql/2MGzkU+1JLAFuR6aJpLAjHwhmwwg==, tarball: https://registry.npmjs.org/@aws-crypto/util/-/util-1.2.2.tgz} + + '@aws-sdk/types@3.609.0': + resolution: {integrity: sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==, tarball: https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-utf8-browser@3.259.0': + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==, tarball: https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz} + '@babel/code-frame@7.24.7': resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==, tarball: https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz} engines: {node: '>=6.9.0'} @@ -916,6 +1002,12 @@ packages: ethers: ^5.0.0 hardhat: ^2.0.0 + '@nomiclabs/hardhat-etherscan@3.1.8': + resolution: {integrity: sha512-v5F6IzQhrsjHh6kQz4uNrym49brK9K5bYCq2zQZ729RYRaifI9hHbtmK+KkIVevfhut7huQFEQ77JLRMAzWYjQ==, tarball: https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.8.tgz} + deprecated: The @nomiclabs/hardhat-etherscan package is deprecated, please use @nomicfoundation/hardhat-verify instead + peerDependencies: + hardhat: ^2.0.4 + '@npmcli/promise-spawn@6.0.2': resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==, tarball: https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -926,6 +1018,27 @@ packages: '@openzeppelin/contracts@4.9.6': resolution: {integrity: sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==, tarball: https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz} + '@openzeppelin/defender-base-client@1.54.6': + resolution: {integrity: sha512-PTef+rMxkM5VQ7sLwLKSjp2DBakYQd661ZJiSRywx+q/nIpm3B/HYGcz5wPZCA5O/QcEP6TatXXDoeMwimbcnw==, tarball: https://registry.npmjs.org/@openzeppelin/defender-base-client/-/defender-base-client-1.54.6.tgz} + deprecated: This package has been deprecated and will no longer be maintained, please use @openzeppelin/defender-sdk package instead. + + '@openzeppelin/hardhat-upgrades@1.28.0': + resolution: {integrity: sha512-7sb/Jf+X+uIufOBnmHR0FJVWuxEs2lpxjJnLNN6eCJCP8nD0v+Ot5lTOW2Qb/GFnh+fLvJtEkhkowz4ZQ57+zQ==, tarball: https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.28.0.tgz} + hasBin: true + peerDependencies: + '@nomiclabs/hardhat-ethers': ^2.0.0 + '@nomiclabs/hardhat-etherscan': ^3.1.0 + '@nomiclabs/harhdat-etherscan': '*' + ethers: ^5.0.5 + hardhat: ^2.0.2 + peerDependenciesMeta: + '@nomiclabs/harhdat-etherscan': + optional: true + + '@openzeppelin/platform-deploy-client@0.8.0': + resolution: {integrity: sha512-POx3AsnKwKSV/ZLOU/gheksj0Lq7Is1q2F3pKmcFjGZiibf+4kjGxr4eSMrT+2qgKYZQH1ZLQZ+SkbguD8fTvA==, tarball: https://registry.npmjs.org/@openzeppelin/platform-deploy-client/-/platform-deploy-client-0.8.0.tgz} + deprecated: '@openzeppelin/platform-deploy-client is deprecated. Please use @openzeppelin/defender-sdk-deploy-client' + '@openzeppelin/upgrades-core@1.27.0': resolution: {integrity: sha512-FBIuFPKiRNMhW09HS8jkmV5DueGfxO2wp/kmCa0m0SMDyX4ROumgy/4Ao0/yH8/JZZPDiH1q3EnTRn+B7TGYgg==, tarball: https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.27.0.tgz} hasBin: true @@ -994,6 +1107,10 @@ packages: '@sinonjs/text-encoding@0.7.2': resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==, tarball: https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz} + '@smithy/types@3.3.0': + resolution: {integrity: sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==, tarball: https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz} + engines: {node: '>=16.0.0'} + '@ts-morph/common@0.23.0': resolution: {integrity: sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==, tarball: https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz} @@ -1204,6 +1321,9 @@ packages: ajv@8.16.0: resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==, tarball: https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz} + amazon-cognito-identity-js@6.3.12: + resolution: {integrity: sha512-s7NKDZgx336cp+oDeUtB2ZzT8jWJp/v2LWuYl+LQtMEODe22RF1IJ4nRiDATp+rp1pTffCZcm44Quw4jx2bqNg==, tarball: https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.12.tgz} + ansi-colors@4.1.1: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==, tarball: https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz} engines: {node: '>=6'} @@ -1303,6 +1423,9 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==, tarball: https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz} engines: {node: '>=8'} + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==, tarball: https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, tarball: https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz} @@ -1404,6 +1527,9 @@ packages: buffer-xor@1.0.3: resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==, tarball: https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz} + buffer@4.9.2: + resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==, tarball: https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, tarball: https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz} @@ -1993,6 +2119,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==, tarball: https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz} engines: {node: '>=4'} + fast-base64-decode@1.0.0: + resolution: {integrity: sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==, tarball: https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, tarball: https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} @@ -2444,6 +2573,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, tarball: https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz} + isomorphic-unfetch@3.1.0: + resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==, tarball: https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, tarball: https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz} engines: {node: '>=8'} @@ -2460,6 +2592,9 @@ packages: resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==, tarball: https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz} engines: {node: '>=14'} + js-cookie@2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==, tarball: https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz} + js-sdsl@4.4.2: resolution: {integrity: sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==, tarball: https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz} @@ -3099,6 +3234,10 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==, tarball: https://registry.npmjs.org/retry/-/retry-0.12.0.tgz} engines: {node: '>= 4'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==, tarball: https://registry.npmjs.org/retry/-/retry-0.13.1.tgz} + engines: {node: '>= 4'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, tarball: https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3550,6 +3689,9 @@ packages: resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==, tarball: https://registry.npmjs.org/undici/-/undici-6.19.2.tgz} engines: {node: '>=18.17'} + unfetch@4.2.0: + resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==, tarball: https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, tarball: https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz} engines: {node: '>= 4.0.0'} @@ -3754,6 +3896,27 @@ packages: snapshots: + '@aws-crypto/sha256-js@1.2.2': + dependencies: + '@aws-crypto/util': 1.2.2 + '@aws-sdk/types': 3.609.0 + tslib: 1.14.1 + + '@aws-crypto/util@1.2.2': + dependencies: + '@aws-sdk/types': 3.609.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + + '@aws-sdk/types@3.609.0': + dependencies: + '@smithy/types': 3.3.0 + tslib: 2.6.3 + + '@aws-sdk/util-utf8-browser@3.259.0': + dependencies: + tslib: 2.6.3 + '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 @@ -4578,6 +4741,22 @@ snapshots: ethers: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) hardhat: 2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10) + '@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10))': + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/address': 5.7.0 + cbor: 8.1.0 + chalk: 2.4.2 + debug: 4.3.5 + fs-extra: 7.0.1 + hardhat: 2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10) + lodash: 4.17.21 + semver: 6.3.1 + table: 6.8.2 + undici: 5.28.4 + transitivePeerDependencies: + - supports-color + '@npmcli/promise-spawn@6.0.2': dependencies: which: 3.0.1 @@ -4586,6 +4765,44 @@ snapshots: '@openzeppelin/contracts@4.9.6': {} + '@openzeppelin/defender-base-client@1.54.6(debug@4.3.5)': + dependencies: + amazon-cognito-identity-js: 6.3.12 + async-retry: 1.3.3 + axios: 1.7.2(debug@4.3.5) + lodash: 4.17.21 + node-fetch: 2.7.0 + transitivePeerDependencies: + - debug + - encoding + + '@openzeppelin/hardhat-upgrades@1.28.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)))(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10))': + dependencies: + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-etherscan': 3.1.8(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@openzeppelin/defender-base-client': 1.54.6(debug@4.3.5) + '@openzeppelin/platform-deploy-client': 0.8.0(debug@4.3.5) + '@openzeppelin/upgrades-core': 1.29.0 + chalk: 4.1.2 + debug: 4.3.5 + ethers: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + hardhat: 2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10) + proper-lockfile: 4.1.2 + transitivePeerDependencies: + - encoding + - supports-color + + '@openzeppelin/platform-deploy-client@0.8.0(debug@4.3.5)': + dependencies: + '@ethersproject/abi': 5.7.0 + '@openzeppelin/defender-base-client': 1.54.6(debug@4.3.5) + axios: 0.21.4(debug@4.3.5) + lodash: 4.17.21 + node-fetch: 2.7.0 + transitivePeerDependencies: + - debug + - encoding + '@openzeppelin/upgrades-core@1.27.0': dependencies: cbor: 8.1.0 @@ -4699,6 +4916,10 @@ snapshots: '@sinonjs/text-encoding@0.7.2': {} + '@smithy/types@3.3.0': + dependencies: + tslib: 2.6.3 + '@ts-morph/common@0.23.0': dependencies: fast-glob: 3.3.2 @@ -4943,6 +5164,16 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 + amazon-cognito-identity-js@6.3.12: + dependencies: + '@aws-crypto/sha256-js': 1.2.2 + buffer: 4.9.2 + fast-base64-decode: 1.0.0 + isomorphic-unfetch: 3.1.0 + js-cookie: 2.2.1 + transitivePeerDependencies: + - encoding + ansi-colors@4.1.1: {} ansi-colors@4.1.3: {} @@ -5053,13 +5284,17 @@ snapshots: astral-regex@2.0.0: {} + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + asynckit@0.4.0: {} available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 - axios@0.21.4: + axios@0.21.4(debug@4.3.5): dependencies: follow-redirects: 1.15.6(debug@4.3.5) transitivePeerDependencies: @@ -5172,6 +5407,12 @@ snapshots: buffer-xor@1.0.3: {} + buffer@4.9.2: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + isarray: 1.0.0 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -5973,6 +6214,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-base64-decode@1.0.0: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -6475,6 +6718,13 @@ snapshots: isexe@2.0.0: {} + isomorphic-unfetch@3.1.0: + dependencies: + node-fetch: 2.7.0 + unfetch: 4.2.0 + transitivePeerDependencies: + - encoding + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -6494,6 +6744,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + js-cookie@2.2.1: {} + js-sdsl@4.4.2: {} js-sha3@0.8.0: {} @@ -7154,6 +7406,8 @@ snapshots: retry@0.12.0: {} + retry@0.13.1: {} + reusify@1.0.4: {} rimraf@2.7.1: @@ -7652,6 +7906,8 @@ snapshots: undici@6.19.2: {} + unfetch@4.2.0: {} + universalify@0.1.2: {} universalify@2.0.1: {} @@ -7857,7 +8113,7 @@ snapshots: zksync@0.13.1(@ethersproject/logger@5.7.0)(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)): dependencies: '@ethersproject/logger': 5.7.0 - axios: 0.21.4 + axios: 0.21.4(debug@4.3.5) ethers: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) websocket: 1.0.35 websocket-as-promised: 1.1.0 From 43846faa4d46aab40462053a2b342725095a574e Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Fri, 23 Aug 2024 14:43:47 +0200 Subject: [PATCH 02/11] fix: refactor code and logic and fix types --- examples/basic-example/package.json | 2 +- examples/deploy-example/package.json | 2 +- .../upgradable-example-l1/hardhat.config.ts | 5 +- .../scripts/upgrade-box-beacon.ts | 4 +- examples/upgradable-example/package.json | 2 +- packages/hardhat-zksync-deploy/package.json | 2 +- .../hardhat-zksync-upgradable/package.json | 5 +- .../src/extension-generator.ts | 109 +----------- .../src/generator.ts | 21 +++ .../hardhat-zksync-upgradable/src/index.ts | 25 +-- .../src/interfaces.ts | 55 +----- .../extension-generator.ts | 90 ++++++++++ .../interfaces.ts | 60 +++++++ .../platform/propose-upgrade.ts | 79 +++++++++ .../platform/utils.ts | 161 ++++++++++++++++++ .../src/proxy-upgrade/upgrade-beacon.ts | 2 +- .../src/proxy-upgrade/upgrade-proxy.ts | 2 +- .../src/type-extensions.ts | 19 ++- .../hardhat-zksync-upgradable/src/utils.ts | 56 +++--- .../src/verify/verify-proxy.ts | 4 + packages/hardhat-zksync/package.json | 4 +- pnpm-lock.yaml | 40 ++--- 22 files changed, 516 insertions(+), 233 deletions(-) create mode 100644 packages/hardhat-zksync-upgradable/src/generator.ts create mode 100644 packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts create mode 100644 packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/interfaces.ts create mode 100644 packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/propose-upgrade.ts create mode 100644 packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/utils.ts diff --git a/examples/basic-example/package.json b/examples/basic-example/package.json index 894b2e034..a175b1aa1 100644 --- a/examples/basic-example/package.json +++ b/examples/basic-example/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.2", "chalk": "^4.1.2", "hardhat": "^2.14.0", "ethers": "~5.7.2", diff --git a/examples/deploy-example/package.json b/examples/deploy-example/package.json index 854138488..a9369872e 100644 --- a/examples/deploy-example/package.json +++ b/examples/deploy-example/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.2", "chalk": "^4.1.2", "hardhat": "^2.14.0", "ethers": "~5.7.2", diff --git a/examples/upgradable-example-l1/hardhat.config.ts b/examples/upgradable-example-l1/hardhat.config.ts index b84003f5b..5063be5af 100644 --- a/examples/upgradable-example-l1/hardhat.config.ts +++ b/examples/upgradable-example-l1/hardhat.config.ts @@ -13,14 +13,15 @@ const config: HardhatUserConfig = { }, }, }, - defaultNetwork:'sepolia', + defaultNetwork:'hardhat', networks: { hardhat: { zksync: false, }, eth: { - zksync: true, + zksync: false, url: 'http://0.0.0.0:8545', + accounts: ['0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'] }, zkSyncNetwork: { zksync: true, diff --git a/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts b/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts index f920774c1..527ecc243 100644 --- a/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts +++ b/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts @@ -12,10 +12,12 @@ async function main() { // upgrade beacon - const boxV2 = await hre.ethers.getContractFactory('Box'); + const boxV2 = await hre.ethers.getContractFactory('BoxV2'); const boxV2Upgraded = await hre.upgrades.upgradeBeacon(beacon.address, boxV2); console.info(chalk.green('Successfully upgraded beacon Box to BoxV2 on address: ', beacon.address)); + await boxV2Upgraded.deployed(); + console.log(boxV2Upgraded.signer); // wait some time before the next call await new Promise((resolve) => setTimeout(resolve, 2000)); const value = await boxV2Upgraded.retrieve(); diff --git a/examples/upgradable-example/package.json b/examples/upgradable-example/package.json index 754e549ae..73b521cad 100644 --- a/examples/upgradable-example/package.json +++ b/examples/upgradable-example/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.2", "@matterlabs/hardhat-zksync-upgradable": "workspace:^", "@openzeppelin/contracts-upgradeable": "^4.9.2", "zksync": "^0.13.1", diff --git a/packages/hardhat-zksync-deploy/package.json b/packages/hardhat-zksync-deploy/package.json index 523f32f7a..73e3223a6 100644 --- a/packages/hardhat-zksync-deploy/package.json +++ b/packages/hardhat-zksync-deploy/package.json @@ -33,7 +33,7 @@ "README.md" ], "dependencies": { - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.2", "chalk": "^4.1.2", "ts-morph": "^22.0.0", "chai": "^4.3.4", diff --git a/packages/hardhat-zksync-upgradable/package.json b/packages/hardhat-zksync-upgradable/package.json index 0b1157429..d0cbfde6a 100644 --- a/packages/hardhat-zksync-upgradable/package.json +++ b/packages/hardhat-zksync-upgradable/package.json @@ -34,10 +34,13 @@ ], "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.2", "@openzeppelin/upgrades-core": "~1.29.0", "@openzeppelin/hardhat-upgrades": "^1.28.0", "@openzeppelin/contracts-hardhat-zksync-upgradable": "npm:@openzeppelin/contracts@^4.9.2", + "@openzeppelin/defender-base-client": "^1.46.0", + "@openzeppelin/platform-deploy-client": "^0.8.0", + "@nomiclabs/hardhat-ethers": "^2.2.3", "chalk": "^4.1.2", "fs-extra": "^11.2.0", "dockerode": "^3.3.4", diff --git a/packages/hardhat-zksync-upgradable/src/extension-generator.ts b/packages/hardhat-zksync-upgradable/src/extension-generator.ts index 16bcd7ea2..f3f00c156 100644 --- a/packages/hardhat-zksync-upgradable/src/extension-generator.ts +++ b/packages/hardhat-zksync-upgradable/src/extension-generator.ts @@ -1,27 +1,11 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { lazyObject } from 'hardhat/plugins'; -import { wrapMakeFunction } from './utils'; -import { HardhatUpgrades, HardhatUpgradesOZ, makeUndefinedFunction, PlatformHardhatUpgrades } from './interfaces'; +import { makeUndefinedFunction, wrapMakeFunction } from './utils'; +import { HardhatUpgrades } from './interfaces'; +import { HardhatUpgradesOZ } from './openzeppelin-hardhat-upgrades/interfaces'; +import { Generator } from './generator'; -interface Generator { - populateExtension(): any; -} - -export class ExtensionGenerator { - constructor(private _hre: HardhatRuntimeEnvironment) {} - - public populatedExtension(): any { - if (this._hre.network.zksync) { - const zkSyncGenerator = new ZkSyncGenerator(this._hre); - return zkSyncGenerator.populateExtension(); - } else { - const openzeppelinGenerator = new OpenZeppelinGenerator(this._hre); - return openzeppelinGenerator.populateExtension(); - } - } -} - -class ZkSyncGenerator implements Generator { +export class ZkSyncGenerator implements Generator { constructor(private _hre: HardhatRuntimeEnvironment) {} private makeFunctions(): HardhatUpgrades { @@ -75,86 +59,3 @@ class ZkSyncGenerator implements Generator { this._hre.zkUpgrades = lazyObject(() => this.makeFunctions()); } } - -class OpenZeppelinGenerator implements Generator { - constructor(private _hre: HardhatRuntimeEnvironment) {} - - public populateExtension(): void { - this._hre.upgrades = lazyObject(() => this.makeFunctions(false)); - this._hre.platform = lazyObject(() => this.makePlatformFunctions(this._hre)); - } - - private makeFunctions(platform: boolean) { - const { - silenceWarnings, - getAdminAddress, - getImplementationAddress, - getBeaconAddress, - } = require('@openzeppelin/upgrades-core'); - const { makeDeployProxy } = require('@openzeppelin/hardhat-upgrades/dist/deploy-proxy'); - const { makeDeployProxyAdmin } = require('@openzeppelin/hardhat-upgrades/dist/deploy-proxy-admin'); - const { makeUpgradeProxy } = require('@openzeppelin/hardhat-upgrades/dist/upgrade-proxy'); - const { makeValidateImplementation } = require('@openzeppelin/hardhat-upgrades/dist/validate-implementation'); - const { makeValidateUpgrade } = require('@openzeppelin/hardhat-upgrades/dist/validate-upgrade'); - const { makeDeployImplementation } = require('@openzeppelin/hardhat-upgrades/dist/deploy-implementation'); - const { makePrepareUpgrade } = require('@openzeppelin/hardhat-upgrades/dist/prepare-upgrade'); - const { makeDeployBeacon } = require('@openzeppelin/hardhat-upgrades/dist/deploy-beacon'); - const { makeDeployBeaconProxy } = require('@openzeppelin/hardhat-upgrades/dist/deploy-beacon-proxy'); - const { makeUpgradeBeacon } = require('@openzeppelin/hardhat-upgrades/dist/upgrade-beacon'); - const { makeForceImport } = require('@openzeppelin/hardhat-upgrades/dist/force-import'); - const { - makeChangeProxyAdmin, - makeTransferProxyAdminOwnership, - makeGetInstanceFunction, - } = require('@openzeppelin/hardhat-upgrades/dist/admin'); - const { getImplementationAddressFromBeacon } = require('@openzeppelin/upgrades-core/dist/impl-address'); - - return { - silenceWarnings, - deployProxy: makeDeployProxy(this._hre, platform), - upgradeProxy: makeUpgradeProxy(this._hre, platform), // block on platform - validateImplementation: makeValidateImplementation(this._hre), - validateUpgrade: makeValidateUpgrade(this._hre), - deployImplementation: makeDeployImplementation(this._hre, platform), - prepareUpgrade: makePrepareUpgrade(this._hre, platform), - deployBeacon: makeDeployBeacon(this._hre, platform), // block on platform - deployBeaconProxy: makeDeployBeaconProxy(this._hre, platform), - upgradeBeacon: makeUpgradeBeacon(this._hre, platform), // block on platform - deployProxyAdmin: makeDeployProxyAdmin(this._hre, platform), // block on platform - forceImport: makeForceImport(this._hre), - admin: { - getInstance: makeGetInstanceFunction(this._hre), - changeProxyAdmin: makeChangeProxyAdmin(this._hre, platform), // block on platform - transferProxyAdminOwnership: makeTransferProxyAdminOwnership(this._hre, platform), // block on platform - }, - erc1967: { - getAdminAddress: (proxyAddress: string) => getAdminAddress(this._hre.network.provider, proxyAddress), - getImplementationAddress: (proxyAddress: string) => - getImplementationAddress(this._hre.network.provider, proxyAddress), - getBeaconAddress: (proxyAddress: string) => getBeaconAddress(this._hre.network.provider, proxyAddress), - }, - beacon: { - getImplementationAddress: (beaconAddress: string) => - getImplementationAddressFromBeacon(this._hre.network.provider, beaconAddress), - }, - estimation: { - estimateGasProxy: makeUndefinedFunction(), - estimateGasBeacon: makeUndefinedFunction(), - estimateGasBeaconProxy: makeUndefinedFunction(), - }, - }; - } - - private makePlatformFunctions(hre: HardhatRuntimeEnvironment): PlatformHardhatUpgrades { - const { makeDeployContract } = require('./deploy-contract'); - const { makeProposeUpgrade } = require('./platform/propose-upgrade'); - const { makeGetDefaultApprovalProcess } = require('./platform/get-default-approval-process'); - - return { - ...this.makeFunctions(true), - deployContract: makeDeployContract(hre, true), - proposeUpgrade: makeProposeUpgrade(hre, true), - getDefaultApprovalProcess: makeGetDefaultApprovalProcess(hre), - }; - } -} diff --git a/packages/hardhat-zksync-upgradable/src/generator.ts b/packages/hardhat-zksync-upgradable/src/generator.ts new file mode 100644 index 000000000..b494d802f --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/generator.ts @@ -0,0 +1,21 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { OpenzeppelinGenerator } from './openzeppelin-hardhat-upgrades/extension-generator'; +import { ZkSyncGenerator } from './extension-generator'; + +export interface Generator { + populateExtension(): any; +} + +export class ExtensionGenerator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + public populatedExtension(): any { + if (this._hre.network.zksync) { + const zkSyncGenerator = new ZkSyncGenerator(this._hre); + return zkSyncGenerator.populateExtension(); + } else { + const openzeppelinGenerator = new OpenzeppelinGenerator(this._hre); + return openzeppelinGenerator.populateExtension(); + } + } +} diff --git a/packages/hardhat-zksync-upgradable/src/index.ts b/packages/hardhat-zksync-upgradable/src/index.ts index 84b38cf12..87c71b571 100644 --- a/packages/hardhat-zksync-upgradable/src/index.ts +++ b/packages/hardhat-zksync-upgradable/src/index.ts @@ -1,12 +1,10 @@ import '@matterlabs/hardhat-zksync-solc'; +import '@nomiclabs/hardhat-ethers'; import './type-extensions'; import { extendEnvironment, subtask, task, types } from 'hardhat/internal/core/config/config-env'; -import { - TASK_COMPILE_SOLIDITY_COMPILE, - TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, -} from 'hardhat/builtin-tasks/task-names'; +import { TASK_COMPILE_SOLIDITY_COMPILE } from 'hardhat/builtin-tasks/task-names'; import { RunCompilerArgs } from './interfaces'; import { extendCompilerOutputSelection, isFullZkSolcOutput } from './utils/utils-general'; @@ -18,9 +16,8 @@ import { TASK_UPGRADE_ZKSYNC_BEACON, TASK_UPGRADE_ZKSYNC_PROXY, } from './task-names'; -import { getUpgradableContracts } from './utils'; -import { ExtensionGenerator } from './extension-generator'; +import { ExtensionGenerator } from './generator'; extendEnvironment((hre) => { const extesionGenerator = new ExtensionGenerator(hre); @@ -103,22 +100,6 @@ subtask(TASK_COMPILE_SOLIDITY_COMPILE, async (args: RunCompilerArgs, hre, runSup return { output, solcBuild }; }); -subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, async (args: RunCompilerArgs, _, runSuper) => { - const sourceNames = await runSuper(); - - const upgradableContracts = getUpgradableContracts(); - return [ - ...sourceNames, - ...[ - upgradableContracts.ProxyAdmin, - upgradableContracts.TransparentUpgradeableProxy, - upgradableContracts.BeaconProxy, - upgradableContracts.UpgradeableBeacon, - upgradableContracts.ERC1967Proxy, - ], - ]; -}); - subtask('verify:verify').setAction(async (args, hre, runSuper) => { const { verify } = await import('./verify/verify-proxy'); return await verify(args, hre, runSuper); diff --git a/packages/hardhat-zksync-upgradable/src/interfaces.ts b/packages/hardhat-zksync-upgradable/src/interfaces.ts index fc1a2258f..0fce03570 100644 --- a/packages/hardhat-zksync-upgradable/src/interfaces.ts +++ b/packages/hardhat-zksync-upgradable/src/interfaces.ts @@ -2,18 +2,6 @@ import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'; import * as zk from 'zksync-ethers'; -import { DeployFunction as DeployFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-proxy'; -import { UpgradeFunction as UpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/upgrade-proxy'; -import { ValidateUpgradeFunction as ValidateUpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/validate-upgrade'; -import { DeployImplementationFunction as DeployImplementationFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-implementation'; -import { PrepareUpgradeFunction as PrepareUpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/prepare-upgrade'; -import { DeployBeaconFunction as DeployBeaconFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-beacon'; -import { DeployBeaconProxyFunction as DeployBeaconProxyFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-beacon-proxy'; -import { UpgradeBeaconFunction as UpgradeBeaconFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/upgrade-beacon'; -import { ForceImportFunction as ForceImportFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/force-import'; -import { DeployContractFunction as DeployContractFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-contract'; -import { GetDefaultApprovalProcessFunction as GetDefaultApprovalProcessFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/platform/get-default-approval-process'; -import { ValidateImplementationFunction as ValidateImplementationFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/validate-implementation'; import { EstimateProxyGasFunction } from './gas-estimation/estimate-gas-proxy'; import { EstimateBeaconGasFunction } from './gas-estimation/estimate-gas-beacon-proxy'; import { ChangeAdminFunction, GetInstanceFunction, TransferProxyAdminOwnershipFunction } from './admin'; @@ -28,54 +16,13 @@ import { import { UpgradeBeaconArtifact, UpgradeBeaconFactory } from './proxy-upgrade/upgrade-beacon'; import { UpgradeProxyArtifact, UpgradeProxyFactory } from './proxy-upgrade/upgrade-proxy'; import { DeployAdminFunction } from './proxy-deployment/deploy-proxy-admin'; - -export type UndefinedFunctionType = (...args: any[]) => any; - -export function makeUndefinedFunction(): UndefinedFunctionType { - return (..._: any[]) => { - throw new Error('This function is not implemented'); - }; -} +import { UndefinedFunctionType } from './utils'; export type ValidateImplementationFunction = ( ImplFactory: zk.ContractFactory, opts?: ValidateImplementationOptions, ) => Promise; -export interface PlatformHardhatUpgrades extends HardhatUpgrades { - deployContract: DeployContractFunctionOZ; - proposeUpgrade: any; - getDefaultApprovalProcess: GetDefaultApprovalProcessFunctionOZ; -} - -export interface HardhatUpgradesOZ { - deployProxy: DeployFunctionOZ; - upgradeProxy: UpgradeFunctionOZ; - validateImplementation: ValidateImplementationFunctionOZ; - validateUpgrade: ValidateUpgradeFunctionOZ; - deployImplementation: DeployImplementationFunctionOZ; - prepareUpgrade: PrepareUpgradeFunctionOZ; - deployBeacon: DeployBeaconFunctionOZ; - deployBeaconProxy: DeployBeaconProxyFunctionOZ; - upgradeBeacon: UpgradeBeaconFunctionOZ; - deployProxyAdmin: DeployAdminFunction; - forceImport: ForceImportFunctionOZ; - silenceWarnings: any; - admin: { - getInstance: GetInstanceFunction; - changeProxyAdmin: ChangeAdminFunction; - transferProxyAdminOwnership: TransferProxyAdminOwnershipFunction; - }; - erc1967: { - getAdminAddress: (proxyAdress: string) => Promise; - getImplementationAddress: (proxyAdress: string) => Promise; - getBeaconAddress: (proxyAdress: string) => Promise; - }; - beacon: { - getImplementationAddress: (beaconAddress: string) => Promise; - }; -} - export interface HardhatUpgrades { deployProxy: DeployFunctionArtifact & DeployFunctionFactory & DeployFunctionFactoryNoArgs; upgradeProxy: UpgradeProxyFactory & UpgradeProxyArtifact; diff --git a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts new file mode 100644 index 000000000..8757bc83b --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts @@ -0,0 +1,90 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { lazyObject } from 'hardhat/plugins'; +import { makeUndefinedFunction } from '../utils'; +import { Generator } from '../generator'; +import { PlatformHardhatUpgradesOZ } from './interfaces'; + +export class OpenzeppelinGenerator implements Generator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + public populateExtension(): void { + this._hre.upgrades = lazyObject(() => this.makeFunctions(false)); + this._hre.platform = lazyObject(() => this.makePlatformFunctions(this._hre)); + } + + private makeFunctions(platform: boolean) { + const { + silenceWarnings, + getAdminAddress, + getImplementationAddress, + getBeaconAddress, + } = require('@openzeppelin/upgrades-core'); + const { makeDeployProxy } = require('@openzeppelin/hardhat-upgrades/dist/deploy-proxy'); + const { makeDeployProxyAdmin } = require('@openzeppelin/hardhat-upgrades/dist/deploy-proxy-admin'); + const { makeUpgradeProxy } = require('@openzeppelin/hardhat-upgrades/dist/upgrade-proxy'); + const { makeValidateImplementation } = require('@openzeppelin/hardhat-upgrades/dist/validate-implementation'); + const { makeValidateUpgrade } = require('@openzeppelin/hardhat-upgrades/dist/validate-upgrade'); + const { makeDeployImplementation } = require('@openzeppelin/hardhat-upgrades/dist/deploy-implementation'); + const { makePrepareUpgrade } = require('@openzeppelin/hardhat-upgrades/dist/prepare-upgrade'); + const { makeDeployBeacon } = require('@openzeppelin/hardhat-upgrades/dist/deploy-beacon'); + const { makeDeployBeaconProxy } = require('@openzeppelin/hardhat-upgrades/dist/deploy-beacon-proxy'); + const { makeUpgradeBeacon } = require('@openzeppelin/hardhat-upgrades/dist/upgrade-beacon'); + const { makeForceImport } = require('@openzeppelin/hardhat-upgrades/dist/force-import'); + const { + makeChangeProxyAdmin, + makeTransferProxyAdminOwnership, + makeGetInstanceFunction, + } = require('@openzeppelin/hardhat-upgrades/dist/admin'); + const { getImplementationAddressFromBeacon } = require('@openzeppelin/upgrades-core/dist/impl-address'); + + return { + silenceWarnings, + deployProxy: makeDeployProxy(this._hre, platform), + upgradeProxy: makeUpgradeProxy(this._hre, platform), // block on platform + validateImplementation: makeValidateImplementation(this._hre), + validateUpgrade: makeValidateUpgrade(this._hre), + deployImplementation: makeDeployImplementation(this._hre, platform), + prepareUpgrade: makePrepareUpgrade(this._hre, platform), + deployBeacon: makeDeployBeacon(this._hre, platform), // block on platform + deployBeaconProxy: makeDeployBeaconProxy(this._hre, platform), + upgradeBeacon: makeUpgradeBeacon(this._hre, platform), // block on platform + deployProxyAdmin: makeDeployProxyAdmin(this._hre, platform), // block on platform + forceImport: makeForceImport(this._hre), + admin: { + getInstance: makeGetInstanceFunction(this._hre), + changeProxyAdmin: makeChangeProxyAdmin(this._hre, platform), // block on platform + transferProxyAdminOwnership: makeTransferProxyAdminOwnership(this._hre, platform), // block on platform + }, + erc1967: { + getAdminAddress: (proxyAddress: string) => getAdminAddress(this._hre.network.provider, proxyAddress), + getImplementationAddress: (proxyAddress: string) => + getImplementationAddress(this._hre.network.provider, proxyAddress), + getBeaconAddress: (proxyAddress: string) => getBeaconAddress(this._hre.network.provider, proxyAddress), + }, + beacon: { + getImplementationAddress: (beaconAddress: string) => + getImplementationAddressFromBeacon(this._hre.network.provider, beaconAddress), + }, + estimation: { + estimateGasProxy: makeUndefinedFunction(), + estimateGasBeacon: makeUndefinedFunction(), + estimateGasBeaconProxy: makeUndefinedFunction(), + }, + }; + } + + private makePlatformFunctions(hre: HardhatRuntimeEnvironment): PlatformHardhatUpgradesOZ { + const { makeDeployContract } = require('@openzeppelin/hardhat-upgrades/dist/deploy-contract'); + const { makeProposeUpgrade } = require('./platform/propose-upgrade'); + const { + makeGetDefaultApprovalProcess, + } = require('@openzeppelin/hardhat-upgrades/dist/get-default-approval-process'); + + return { + ...this.makeFunctions(true), + deployContract: makeDeployContract(hre, true), + proposeUpgrade: makeProposeUpgrade(hre, true), + getDefaultApprovalProcess: makeGetDefaultApprovalProcess(hre), + }; + } +} diff --git a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/interfaces.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/interfaces.ts new file mode 100644 index 000000000..6b71d4e21 --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/interfaces.ts @@ -0,0 +1,60 @@ +import { DeployFunction as DeployFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-proxy'; +import { UpgradeFunction as UpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/upgrade-proxy'; +import { ValidateUpgradeFunction as ValidateUpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/validate-upgrade'; +import { DeployImplementationFunction as DeployImplementationFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-implementation'; +import { PrepareUpgradeFunction as PrepareUpgradeFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/prepare-upgrade'; +import { DeployBeaconFunction as DeployBeaconFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-beacon'; +import { DeployBeaconProxyFunction as DeployBeaconProxyFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-beacon-proxy'; +import { UpgradeBeaconFunction as UpgradeBeaconFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/upgrade-beacon'; +import { ForceImportFunction as ForceImportFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/force-import'; +import { + GetInstanceFunction as GetInstanceFunctionOZ, + ChangeAdminFunction as ChangeAdminFunctionOZ, + TransferProxyAdminOwnershipFunction as TransferProxyAdminOwnershipFunctionOZ, +} from '@openzeppelin/hardhat-upgrades/dist/admin'; +import { DeployContractFunction as DeployContractFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-contract'; +import { GetDefaultApprovalProcessFunction as GetDefaultApprovalProcessFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/platform/get-default-approval-process'; +import { ValidateImplementationFunction as ValidateImplementationFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/validate-implementation'; +import { silenceWarnings } from '@openzeppelin/upgrades-core'; +import { DeployAdminFunction as DeployAdminFunctionOZ } from '@openzeppelin/hardhat-upgrades/dist/deploy-proxy-admin'; +import { ProposeUpgradeFunction as ProposeUpgradeFunctionOZ } from './platform/propose-upgrade'; + +export interface PlatformHardhatUpgradesOZ extends HardhatUpgradesOZ { + deployContract: DeployContractFunctionOZ; + proposeUpgrade: ProposeUpgradeFunctionOZ; + getDefaultApprovalProcess: GetDefaultApprovalProcessFunctionOZ; +} + +export interface HardhatUpgradesOZ { + deployProxy: DeployFunctionOZ; + upgradeProxy: UpgradeFunctionOZ; + validateImplementation: ValidateImplementationFunctionOZ; + validateUpgrade: ValidateUpgradeFunctionOZ; + deployImplementation: DeployImplementationFunctionOZ; + prepareUpgrade: PrepareUpgradeFunctionOZ; + deployBeacon: DeployBeaconFunctionOZ; + deployBeaconProxy: DeployBeaconProxyFunctionOZ; + upgradeBeacon: UpgradeBeaconFunctionOZ; + deployProxyAdmin: DeployAdminFunctionOZ; + forceImport: ForceImportFunctionOZ; + silenceWarnings: typeof silenceWarnings; + admin: { + getInstance: GetInstanceFunctionOZ; + changeProxyAdmin: ChangeAdminFunctionOZ; + transferProxyAdminOwnership: TransferProxyAdminOwnershipFunctionOZ; + }; + erc1967: { + getAdminAddress: (proxyAdress: string) => Promise; + getImplementationAddress: (proxyAdress: string) => Promise; + getBeaconAddress: (proxyAdress: string) => Promise; + }; + beacon: { + getImplementationAddress: (beaconAddress: string) => Promise; + }; +} + +export interface HardhatPlatformConfig { + apiKey: string; + apiSecret: string; + usePlatformDeploy?: boolean; +} diff --git a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/propose-upgrade.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/propose-upgrade.ts new file mode 100644 index 000000000..6c7dd4028 --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/propose-upgrade.ts @@ -0,0 +1,79 @@ +import { + getAdminAddress, + getImplementationAddress, + isBeaconProxy, + isTransparentProxy, +} from '@openzeppelin/upgrades-core'; +import { ContractFactory, ethers } from 'ethers'; +import { FormatTypes } from 'ethers/lib/utils'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { PlatformDeployOptions, UpgradeOptions } from '@openzeppelin/hardhat-upgrades/dist/utils'; +import { deployImplForUpgrade } from '@openzeppelin/hardhat-upgrades/dist/prepare-upgrade'; +import { getNetwork, enablePlatform, getPlatformClient } from './utils'; + +export interface UpgradeProposalResponse { + proposalId: string; + url?: string; + txResponse?: ethers.providers.TransactionResponse; +} + +export type ProposeUpgradeFunction = ( + proxyAddress: string, + contractNameOrImplFactory: string | ContractFactory, + opts?: ProposalOptions, +) => Promise; + +export interface ProposalOptions extends UpgradeOptions, PlatformDeployOptions { + approvalProcessId?: string; +} + +export function makeProposeUpgrade(hre: HardhatRuntimeEnvironment, platformModule: boolean): ProposeUpgradeFunction { + return async function proposeUpgrade(proxyAddress, contractNameOrImplFactory, opts = {}) { + opts = enablePlatform(hre, platformModule, opts); + + const client = getPlatformClient(hre); + const network = await getNetwork(hre); + + if (await isBeaconProxy(hre.network.provider, proxyAddress)) { + throw new Error(`Beacon proxy is not currently supported with platform.proposeUpgrade()`); + } else { + // try getting the implementation address so that it will give an error if it's not a transparent/uups proxy + await getImplementationAddress(hre.network.provider, proxyAddress); + } + + let proxyAdmin; + if (await isTransparentProxy(hre.network.provider, proxyAddress)) { + // use the erc1967 admin address as the proxy admin + proxyAdmin = await getAdminAddress(hre.network.provider, proxyAddress); + } + + const implFactory = + typeof contractNameOrImplFactory === 'string' + ? await hre.ethers.getContractFactory(contractNameOrImplFactory) + : contractNameOrImplFactory; + const abi = implFactory.interface.format(FormatTypes.json) as string; + + const deployedImpl = await deployImplForUpgrade(hre, proxyAddress, implFactory, { + getTxResponse: true, + ...opts, + }); + + const txResponse = deployedImpl.txResponse; + const newImplementation = deployedImpl.impl; + + const upgradeProposalResponse = await client.Upgrade.upgrade({ + proxyAddress, + proxyAdminAddress: proxyAdmin, + newImplementationABI: abi, + newImplementationAddress: newImplementation, + network, + approvalProcessId: opts.approvalProcessId, + }); + + return { + proposalId: upgradeProposalResponse.proposalId, + url: upgradeProposalResponse.externalUrl, + txResponse, + }; + }; +} diff --git a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/utils.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/utils.ts new file mode 100644 index 000000000..74bb32052 --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/platform/utils.ts @@ -0,0 +1,161 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { + getChainId, + hasCode, + RemoteDeployment, + DeployOpts, + isDeploymentCompleted, + UpgradesError, +} from '@openzeppelin/upgrades-core'; + +import { Network, fromChainId } from '@openzeppelin/defender-base-client'; +import { + BlockExplorerApiKeyClient, + DeploymentClient, + DeploymentConfigClient, + PlatformClient, + UpgradeClient, +} from '@openzeppelin/platform-deploy-client'; + +import { Platform } from '@openzeppelin/hardhat-upgrades/dist/utils'; +import debug from '@openzeppelin/hardhat-upgrades/dist/utils/debug'; + +import { promisify } from 'util'; +import { HardhatPlatformConfig } from '../interfaces'; +const sleep = promisify(setTimeout); + +export function getPlatformApiKey(hre: HardhatRuntimeEnvironment): HardhatPlatformConfig { + const cfg = hre.config.platform; + if (!cfg || !cfg.apiKey || !cfg.apiSecret) { + const sampleConfig = JSON.stringify({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' }, null, 2); + throw new Error( + `Missing OpenZeppelin Platform API key and secret in hardhat config. Add the following to your hardhat.config.js configuration:\nplatform: ${sampleConfig}\n`, + ); + } + return cfg; +} + +export async function getNetwork(hre: HardhatRuntimeEnvironment): Promise { + const { provider } = hre.network; + const chainId = hre.network.config.chainId ?? (await getChainId(provider)); + const network = fromChainId(chainId); + if (network === undefined) { + throw new Error(`Network ${chainId} is not supported by the OpenZeppelin Platform`); + } + return network; +} + +export function enablePlatform( + hre: HardhatRuntimeEnvironment, + platformModule: boolean, + opts: T, +): T { + if ((hre.config.platform?.usePlatformDeploy || platformModule) && opts.usePlatformDeploy === undefined) { + return { + ...opts, + usePlatformDeploy: true, + }; + } else { + return opts; + } +} + +/** + * Disables Platform for a function that does not support it. + * If opts.usePlatformDeploy or platformModule is true, throws an error. + * If hre.config.platform.usePlatformDeploy is true, logs a debug message and passes (to allow fallback to Hardhat signer). + * + * @param hre The Hardhat runtime environment + * @param platformModule Whether the function was called from the platform module + * @param opts The options passed to the function + * @param unsupportedFunction The name of the function that does not support Platform + */ +export function disablePlatform( + hre: HardhatRuntimeEnvironment, + platformModule: boolean, + opts: Platform, + unsupportedFunction: string, +): void { + if (opts.usePlatformDeploy) { + throw new UpgradesError( + `The function ${unsupportedFunction} is not supported with the \`usePlatformDeploy\` option.`, + ); + } else if (platformModule) { + throw new UpgradesError( + `The function ${unsupportedFunction} is not supported with the \`platform\` module.`, + () => `Call the function as upgrades.${unsupportedFunction} to use the Hardhat signer.`, + ); + } else if (hre.config.platform?.usePlatformDeploy) { + debug( + `The function ${unsupportedFunction} is not supported with the \`platform.usePlatformDeploy\` configuration option. Using the Hardhat signer instead.`, + ); + } +} + +// eslint-disable-next-line @typescript-eslint/no-redeclare +interface PlatformClient { + Deployment: DeploymentClient; + DeploymentConfig: DeploymentConfigClient; + BlockExplorerApiKey: BlockExplorerApiKeyClient; + Upgrade: UpgradeClient; +} + +export function getPlatformClient(hre: HardhatRuntimeEnvironment): PlatformClient { + return PlatformClient(getPlatformApiKey(hre)); +} + +/** + * Gets the remote deployment response for the given id. + * + * @param hre The Hardhat runtime environment + * @param remoteDeploymentId The deployment id. + * @returns The remote deployment response, or undefined if the deployment is not found. + * @throws Error if the deployment response could not be retrieved. + */ +export async function getRemoteDeployment( + hre: HardhatRuntimeEnvironment, + remoteDeploymentId: string, +): Promise { + const client = getPlatformClient(hre); + try { + return (await client.Deployment.get(remoteDeploymentId)) as RemoteDeployment; + } catch (e) { + const message = (e as any).response?.data?.message; + if (message?.match(/deployment with id .* not found\./)) { + return undefined; + } + throw e; + } +} + +/** + * Waits indefinitely for the deployment until it is completed or failed. + * Returns the last known transaction hash seen from the remote deployment, or undefined if the remote deployment was not retrieved. + */ +export async function waitForDeployment( + hre: HardhatRuntimeEnvironment, + opts: DeployOpts, + address: string, + remoteDeploymentId: string, +): Promise { + const pollInterval = opts.pollingInterval ?? 5e3; + let lastKnownTxHash: string | undefined; + + // eslint-disable-next-line no-constant-condition + while (true) { + if (await hasCode(hre.ethers.provider, address)) { + debug('code in target address found', address); + break; + } + + const response = await getRemoteDeployment(hre, remoteDeploymentId); + lastKnownTxHash = response?.txHash; + const completed = await isDeploymentCompleted(address, remoteDeploymentId, response); + if (completed) { + break; + } else { + await sleep(pollInterval); + } + } + return lastKnownTxHash; +} diff --git a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts index 73f24179d..e574558ea 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-beacon.ts @@ -103,7 +103,7 @@ async function upgradeBeacon( export function makeUpgradeBeacon(hre: HardhatRuntimeEnvironment): UpgradeBeaconArtifact | UpgradeBeaconFactory { return async function (...args: Parameters): Promise { - const target = args[0]; + const target = args[1]; if (target instanceof zk.ContractFactory) { return await upgradeBeaconFactory(hre, ...(args as Parameters)); } else { diff --git a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts index 4be6356ab..701c8baeb 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-upgrade/upgrade-proxy.ts @@ -36,7 +36,7 @@ type Upgrader = (nextImpl: string, call?: string) => Promise): Promise { - const target = args[0]; + const target = args[1]; if (target instanceof zk.ContractFactory || args[1] instanceof zk.ContractFactory) { return await upgradeProxyFactory(hre, ...(args as Parameters)); } else { diff --git a/packages/hardhat-zksync-upgradable/src/type-extensions.ts b/packages/hardhat-zksync-upgradable/src/type-extensions.ts index 1ca45611f..b8ce1f949 100644 --- a/packages/hardhat-zksync-upgradable/src/type-extensions.ts +++ b/packages/hardhat-zksync-upgradable/src/type-extensions.ts @@ -1,10 +1,25 @@ import 'hardhat/types/runtime'; -import { HardhatUpgrades, HardhatUpgradesOZ, PlatformHardhatUpgrades } from './interfaces'; +import { HardhatUpgrades } from './interfaces'; +import { + HardhatPlatformConfig, + HardhatUpgradesOZ, + PlatformHardhatUpgradesOZ, +} from './openzeppelin-hardhat-upgrades/interfaces'; declare module 'hardhat/types/runtime' { export interface HardhatRuntimeEnvironment { zkUpgrades: HardhatUpgrades; upgrades: HardhatUpgrades & HardhatUpgradesOZ; - platform: PlatformHardhatUpgrades; + platform: PlatformHardhatUpgradesOZ; + } +} + +declare module 'hardhat/types/config' { + export interface HardhatUserConfig { + platform?: HardhatPlatformConfig; + } + + export interface HardhatConfig { + platform?: HardhatPlatformConfig; } } diff --git a/packages/hardhat-zksync-upgradable/src/utils.ts b/packages/hardhat-zksync-upgradable/src/utils.ts index dbd675acd..df5053487 100644 --- a/packages/hardhat-zksync-upgradable/src/utils.ts +++ b/packages/hardhat-zksync-upgradable/src/utils.ts @@ -24,31 +24,41 @@ export function wrapMakeFunction( wrappedFunction: (...args: any) => T, ): (...args: any) => Promise { return async function (...args: any): Promise { - try { - if (!isOpenzeppelinContractsVersionValid()) { - throw new Error(OZ_CONTRACTS_VERISION_INCOMPATIBLE_ERROR); - } - } catch (e: any) { - console.warn(chalk.yellow(e.message)); - } + checkOpenzeppelinVersion(); - const upgradableContracts = getUpgradableContracts(); - // @ts-ignore - hre.config.zksolc.settings.overrideContractsToCompile = [ - upgradableContracts.ProxyAdmin, - upgradableContracts.TransparentUpgradeableProxy, - upgradableContracts.BeaconProxy, - upgradableContracts.UpgradeableBeacon, - upgradableContracts.ERC1967Proxy, - ]; - await hre.run('compile', { quiet: true }); - // @ts-ignore - hre.config.zksolc.settings.overrideContractsToCompile = undefined; + await compileProxyContracts(hre); return wrappedFunction(...args); }; } +function checkOpenzeppelinVersion() { + try { + if (!isOpenzeppelinContractsVersionValid()) { + throw new Error(OZ_CONTRACTS_VERISION_INCOMPATIBLE_ERROR); + } + } catch (e: any) { + console.warn(chalk.yellow(e.message)); + } +} + +export async function compileProxyContracts(hre: HardhatRuntimeEnvironment, noCompile: boolean = false) { + if (noCompile) { + return; + } + + const upgradableContracts = getUpgradableContracts(); + hre.config.zksolc.settings.forceContractsToCompile = [ + upgradableContracts.ProxyAdmin, + upgradableContracts.TransparentUpgradeableProxy, + upgradableContracts.BeaconProxy, + upgradableContracts.UpgradeableBeacon, + upgradableContracts.ERC1967Proxy, + ]; + await hre.run('compile', { quiet: true }); + delete hre.config.zksolc.settings.forceContractsToCompile; +} + export function isOpenzeppelinContractsVersionValid(): boolean { try { // eslint-disable-next-line import/no-extraneous-dependencies @@ -83,3 +93,11 @@ export function tryRequire(id: string, resolveOnly?: boolean) { } return false; } + +export type UndefinedFunctionType = (...args: any[]) => any; + +export function makeUndefinedFunction(): UndefinedFunctionType { + return (..._: any[]) => { + throw new Error('This function is not implemented'); + }; +} diff --git a/packages/hardhat-zksync-upgradable/src/verify/verify-proxy.ts b/packages/hardhat-zksync-upgradable/src/verify/verify-proxy.ts index e4d7138b4..2aec0940c 100644 --- a/packages/hardhat-zksync-upgradable/src/verify/verify-proxy.ts +++ b/packages/hardhat-zksync-upgradable/src/verify/verify-proxy.ts @@ -9,6 +9,7 @@ import { EVENT_NOT_FOUND_ERROR, UPGRADE_VERIFY_ERROR } from '../constants'; import { getContractCreationTxHash } from '../utils/utils-general'; import { VerifiableContractInfo } from '../interfaces'; import { ZkSyncUpgradablePluginError } from '../errors'; +import { compileProxyContracts } from '../utils'; import { fullVerifyTransparentOrUUPS } from './verify-transparent-uups'; import { fullVerifyBeacon, fullVerifyBeaconProxy } from './verify-beacon'; @@ -29,10 +30,13 @@ export async function verify(args: any, hre: HardhatRuntimeEnvironment, runSuper const proxyAddress = args.address; if (await isTransparentOrUUPSProxy(provider, proxyAddress)) { + await compileProxyContracts(hre, args.noCompile); await fullVerifyTransparentOrUUPS(hre, proxyAddress, hardhatZkSyncVerify, runSuper, args.noCompile); } else if (await isBeaconProxy(provider, proxyAddress)) { + await compileProxyContracts(hre, args.noCompile); await fullVerifyBeaconProxy(hre, proxyAddress, hardhatZkSyncVerify, runSuper, args.noCompile); } else if (await isBeacon(provider, proxyAddress)) { + await compileProxyContracts(hre, args.noCompile); await fullVerifyBeacon(hre, proxyAddress, hardhatZkSyncVerify, runSuper, args.noCompile); } else { return hardhatZkSyncVerify(proxyAddress); diff --git a/packages/hardhat-zksync/package.json b/packages/hardhat-zksync/package.json index 896c8fac8..8854047d6 100644 --- a/packages/hardhat-zksync/package.json +++ b/packages/hardhat-zksync/package.json @@ -54,7 +54,7 @@ }, "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.2", "@matterlabs/hardhat-zksync-verify": "^1.6.0", "@matterlabs/hardhat-zksync-upgradable": "workspace:^", "@matterlabs/hardhat-zksync-node": "^1.1.1", @@ -73,7 +73,7 @@ }, "peerDependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-solc": "^1.2.2", "@matterlabs/hardhat-zksync-upgradable": "workspace:^" }, "prettier": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 369841661..8d8705a27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: workspace:^ version: link:../../packages/hardhat-zksync-deploy '@matterlabs/hardhat-zksync-solc': - specifier: ^1.2.0 - version: 1.2.0(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + specifier: ^1.2.2 + version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@matterlabs/zksync-contracts': specifier: ^0.6.1 version: 0.6.1(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@4.9.6) @@ -103,8 +103,8 @@ importers: specifier: workspace:^ version: link:../../packages/hardhat-zksync-deploy '@matterlabs/hardhat-zksync-solc': - specifier: ^1.2.0 - version: 1.2.0(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + specifier: ^1.2.2 + version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) chalk: specifier: ^4.1.2 version: 4.1.2 @@ -161,8 +161,8 @@ importers: specifier: workspace:^ version: link:../../packages/hardhat-zksync-deploy '@matterlabs/hardhat-zksync-solc': - specifier: ^1.2.0 - version: 1.2.0(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + specifier: ^1.2.2 + version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@matterlabs/hardhat-zksync-upgradable': specifier: workspace:^ version: link:../../packages/hardhat-zksync-upgradable @@ -301,8 +301,8 @@ importers: specifier: ^1.1.1 version: 1.1.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@matterlabs/hardhat-zksync-solc': - specifier: ^1.2.0 - version: 1.2.0(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + specifier: ^1.2.2 + version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@matterlabs/hardhat-zksync-upgradable': specifier: workspace:^ version: link:../hardhat-zksync-upgradable @@ -404,8 +404,8 @@ importers: packages/hardhat-zksync-deploy: dependencies: '@matterlabs/hardhat-zksync-solc': - specifier: ^1.2.0 - version: 1.2.0(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + specifier: ^1.2.2 + version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) chai: specifier: ^4.3.4 version: 4.4.1 @@ -504,8 +504,8 @@ importers: specifier: workspace:^ version: link:../hardhat-zksync-deploy '@matterlabs/hardhat-zksync-solc': - specifier: ^1.2.0 - version: 1.2.0(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + specifier: ^1.2.2 + version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@openzeppelin/contracts-hardhat-zksync-upgradable': specifier: npm:@openzeppelin/contracts@^4.9.2 version: '@openzeppelin/contracts@4.9.6' @@ -869,13 +869,13 @@ packages: peerDependencies: hardhat: ^2.22.5 - '@matterlabs/hardhat-zksync-solc@1.2.0': - resolution: {integrity: sha512-zM3LY6jeCVfFe2MZfiK/6k8GUcxk9BcCBiNs1Ywh4PZ4OaabYOP3HuFFmVo89BFisIRROnQ+IyT9fayKKVbFCg==, tarball: https://registry.npmjs.org/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.2.0.tgz} + '@matterlabs/hardhat-zksync-solc@1.2.1': + resolution: {integrity: sha512-009FEm1qSYTooamd+T8iylIhpk6zT80RnHd9fqZoCWFM49xR1foegAv76oOMyFMsHuSHDbwkWyTSNDo7U5vAzQ==, tarball: https://registry.npmjs.org/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.2.1.tgz} peerDependencies: hardhat: ^2.22.5 - '@matterlabs/hardhat-zksync-solc@1.2.1': - resolution: {integrity: sha512-009FEm1qSYTooamd+T8iylIhpk6zT80RnHd9fqZoCWFM49xR1foegAv76oOMyFMsHuSHDbwkWyTSNDo7U5vAzQ==, tarball: https://registry.npmjs.org/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.2.1.tgz} + '@matterlabs/hardhat-zksync-solc@1.2.2': + resolution: {integrity: sha512-UutCzXuAKh1RDR4NnF5bjmSsDdiUvyzeiQxKTu+ul244bthK2aw0nMeMTeT7XJakrVuLm3futIZiOXQjPieKbw==, tarball: https://registry.npmjs.org/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.2.2.tgz} peerDependencies: hardhat: ^2.22.5 @@ -4456,7 +4456,7 @@ snapshots: '@matterlabs/hardhat-zksync-node@1.1.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10))': dependencies: - '@matterlabs/hardhat-zksync-solc': 1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@matterlabs/hardhat-zksync-solc': 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) axios: 1.7.2(debug@4.3.5) chai: 4.4.1 chalk: 4.1.2 @@ -4471,7 +4471,7 @@ snapshots: - encoding - supports-color - '@matterlabs/hardhat-zksync-solc@1.2.0(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10))': + '@matterlabs/hardhat-zksync-solc@1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10))': dependencies: '@nomiclabs/hardhat-docker': 2.0.2 chai: 4.4.1 @@ -4489,7 +4489,7 @@ snapshots: - encoding - supports-color - '@matterlabs/hardhat-zksync-solc@1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10))': + '@matterlabs/hardhat-zksync-solc@1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10))': dependencies: '@nomiclabs/hardhat-docker': 2.0.2 chai: 4.4.1 @@ -4511,7 +4511,7 @@ snapshots: dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 - '@matterlabs/hardhat-zksync-solc': 1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@matterlabs/hardhat-zksync-solc': 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-verify': 2.0.8(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) axios: 1.7.2(debug@4.3.5) cbor: 9.0.2 From f51f31af9b2f6b85ec9f905f57be750978cf2093 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Mon, 26 Aug 2024 01:02:09 +0200 Subject: [PATCH 03/11] chore: update pnpm-lock.yaml --- pnpm-lock.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d8705a27..5caa83856 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -506,12 +506,21 @@ importers: '@matterlabs/hardhat-zksync-solc': specifier: ^1.2.2 version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@openzeppelin/contracts-hardhat-zksync-upgradable': specifier: npm:@openzeppelin/contracts@^4.9.2 version: '@openzeppelin/contracts@4.9.6' + '@openzeppelin/defender-base-client': + specifier: ^1.46.0 + version: 1.54.6(debug@4.3.5) '@openzeppelin/hardhat-upgrades': specifier: ^1.28.0 version: 1.28.0(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)))(@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)))(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@openzeppelin/platform-deploy-client': + specifier: ^0.8.0 + version: 0.8.0(debug@4.3.5) '@openzeppelin/upgrades-core': specifier: ~1.29.0 version: 1.29.0 From bc883ec9db923598a470e3c0682b5d3615764368 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Mon, 26 Aug 2024 01:47:26 +0200 Subject: [PATCH 04/11] fix: change dependency path and remove unused cache --- .../.openzeppelin/sepolia.json | 421 ------------------ .../scripts/upgrade-box-beacon.ts | 1 - .../extension-generator.ts | 2 +- 3 files changed, 1 insertion(+), 423 deletions(-) delete mode 100644 examples/upgradable-example-l1/.openzeppelin/sepolia.json diff --git a/examples/upgradable-example-l1/.openzeppelin/sepolia.json b/examples/upgradable-example-l1/.openzeppelin/sepolia.json deleted file mode 100644 index 0eb8096ea..000000000 --- a/examples/upgradable-example-l1/.openzeppelin/sepolia.json +++ /dev/null @@ -1,421 +0,0 @@ -{ - "manifestVersion": "3.2", - "admin": { - "address": "0xD7fED3de4E423A7F37F2Be4928DFa95Cd5F4ccd4", - "txHash": "0x7ac43bdf9be81c9100a0debdcf782a52a2fbdf6dd918397be85629e4b892e245" - }, - "proxies": [ - { - "address": "0x35dFB618e5a16D4428740fE378bFc885c6921c5D", - "txHash": "0x88afcdf5fbc47c6293f1a08bb43d88f1a5af9d9160c512c5e7ef08c38ff89034", - "kind": "beacon" - }, - { - "address": "0x5093cc0c7a3121fF3a89015c7DF15B935Bf289A6", - "txHash": "0xe47112a8ac83ec7221cb4579687282119cf8160a7fa9d8ef24cc74082533d9a7", - "kind": "transparent" - }, - { - "address": "0xF564345d479e17135e4504706693953dC0BbF8A9", - "txHash": "0x8d3067173f04abe75b324c033128cd31fffa314fe3c66b01ee045bb951d3113c", - "kind": "uups" - }, - { - "address": "0x51f4c0076110a2797242723D8fB7388D22d55565", - "txHash": "0x8c9ea3dfec68e96ffc70b1b2047c4b94984a7fe34e34db1d34607a6bfc866292", - "kind": "uups" - }, - { - "address": "0x98437F6E0720d1a04140Afb6e941bda24Fc59ab7", - "txHash": "0x5cb9a46a7318a47377a319ef8cc10e762dcd5b5d7a065efd7fa2b9c021f969ab", - "kind": "beacon" - }, - { - "address": "0x70E0E49501EB642a0788A8b10D468680e8ceD32C", - "txHash": "0xd9deabca21d203b98a32ff2ecd77646da9920be635307237cb500f01da822a6e", - "kind": "beacon" - }, - { - "address": "0x58D453922c9036dcA3567A8837AaFeba9AAeb243", - "txHash": "0x08c1ebc63cada80c28bdfb524a588bba5a63c438d31a0ce2736a66a8c50d25ce", - "kind": "transparent" - }, - { - "address": "0x1e52885398D82968381397BFDB30F59Cc34417B7", - "txHash": "0x2c476e1c481749c4a166164ca0cc0de8eebfa67ded33fafa7a9460b9485428de", - "kind": "transparent" - }, - { - "address": "0xDbf63e4495e98877d0CEd3419f5476Be42e2837a", - "txHash": "0x1df840ad26f5669b038d6206e9412f6f59f3205e1e2c642908e10eae3e6a19f1", - "kind": "uups" - }, - { - "address": "0xbc1ae9571623ce46ab1d03e4920b0B8AB10b3442", - "txHash": "0xe4888682c8e8c016853880391e018a4fb4a6d614db96362a2d374c3fd19be8ee", - "kind": "uups" - } - ], - "impls": { - "dba45db98e4df9c5fde9024b207b830f291f1b722d86310f1517175ea9a3658d": { - "address": "0xe36C1a40021Fd0242EEd44eE2860e6Be9FF11D5b", - "txHash": "0x3a755ccd03ea153ca08673f0f14d67a2f3b21bad56e8d25bf4d4401796e6785a", - "layout": { - "solcVersion": "0.8.20", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" - }, - { - "label": "value", - "offset": 0, - "slot": "1", - "type": "t_uint256", - "contract": "Box", - "src": "contracts/Box.sol:7" - }, - { - "label": "secondValue", - "offset": 0, - "slot": "2", - "type": "t_uint256", - "contract": "Box", - "src": "contracts/Box.sol:8" - }, - { - "label": "thirdValue", - "offset": 0, - "slot": "3", - "type": "t_uint256", - "contract": "Box", - "src": "contracts/Box.sol:9" - } - ], - "types": { - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "4ea467f78617a083b45ec1794c89f8aff5f193eca64394a94cfc961b86c1b73f": { - "address": "0x5Cf8d6DfbfD588adB5Ef57a01E657D96C8377935", - "txHash": "0x330b5a87b8b4df51f021a2511ad74c13284d6c607d03ffadc376bf170cf3144d", - "layout": { - "solcVersion": "0.8.20", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "ERC1967UpgradeUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" - }, - { - "label": "__gap", - "offset": 0, - "slot": "51", - "type": "t_array(t_uint256)50_storage", - "contract": "UUPSUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" - }, - { - "label": "__gap", - "offset": 0, - "slot": "101", - "type": "t_array(t_uint256)50_storage", - "contract": "ContextUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" - }, - { - "label": "_owner", - "offset": 0, - "slot": "151", - "type": "t_address", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" - }, - { - "label": "__gap", - "offset": 0, - "slot": "152", - "type": "t_array(t_uint256)49_storage", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" - }, - { - "label": "value", - "offset": 0, - "slot": "201", - "type": "t_uint256", - "contract": "BoxUups", - "src": "contracts/BoxUups.sol:8" - }, - { - "label": "secondValue", - "offset": 0, - "slot": "202", - "type": "t_uint256", - "contract": "BoxUups", - "src": "contracts/BoxUups.sol:9" - }, - { - "label": "thirdValue", - "offset": 0, - "slot": "203", - "type": "t_uint256", - "contract": "BoxUups", - "src": "contracts/BoxUups.sol:10" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_uint256)49_storage": { - "label": "uint256[49]", - "numberOfBytes": "1568" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "ffe53c56a4ef665c89f7119250ec79a1cfea3acb97611a8ae549da62ac7a6bd4": { - "address": "0x33F0E3c63f4df5dd63fab20e8e2e97D792DD7beF", - "txHash": "0x29ca8fdc92316b49d7c68042f0b4193bb0dae8a4e99e213d0eccad0a687a16a3", - "layout": { - "solcVersion": "0.8.20", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" - }, - { - "label": "value", - "offset": 0, - "slot": "1", - "type": "t_uint256", - "contract": "BoxV2", - "src": "contracts/BoxV2.sol:7" - }, - { - "label": "secondValue", - "offset": 0, - "slot": "2", - "type": "t_uint256", - "contract": "BoxV2", - "src": "contracts/BoxV2.sol:8" - }, - { - "label": "thirdValue", - "offset": 0, - "slot": "3", - "type": "t_uint256", - "contract": "BoxV2", - "src": "contracts/BoxV2.sol:9" - } - ], - "types": { - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "cc69a79a8a65be359f2171fe6a8dc4134811ec4fa66c48231744e99f843c0588": { - "address": "0x283A84Bf71Ab691c5AC32907D844ead26270F6c3", - "txHash": "0x5d47d17f5be67ed34e0b03b3e5aa8e847605bd3fa6a35f75cd5d96f87f5f20eb", - "layout": { - "solcVersion": "0.8.20", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "ERC1967UpgradeUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" - }, - { - "label": "__gap", - "offset": 0, - "slot": "51", - "type": "t_array(t_uint256)50_storage", - "contract": "UUPSUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" - }, - { - "label": "__gap", - "offset": 0, - "slot": "101", - "type": "t_array(t_uint256)50_storage", - "contract": "ContextUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" - }, - { - "label": "_owner", - "offset": 0, - "slot": "151", - "type": "t_address", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" - }, - { - "label": "__gap", - "offset": 0, - "slot": "152", - "type": "t_array(t_uint256)49_storage", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" - }, - { - "label": "value", - "offset": 0, - "slot": "201", - "type": "t_uint256", - "contract": "BoxUupsV2", - "src": "contracts/BoxUupsV2.sol:8" - }, - { - "label": "secondValue", - "offset": 0, - "slot": "202", - "type": "t_uint256", - "contract": "BoxUupsV2", - "src": "contracts/BoxUupsV2.sol:9" - }, - { - "label": "thirdValue", - "offset": 0, - "slot": "203", - "type": "t_uint256", - "contract": "BoxUupsV2", - "src": "contracts/BoxUupsV2.sol:10" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_uint256)49_storage": { - "label": "uint256[49]", - "numberOfBytes": "1568" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - } - } -} diff --git a/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts b/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts index 527ecc243..64fae43dd 100644 --- a/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts +++ b/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts @@ -17,7 +17,6 @@ async function main() { console.info(chalk.green('Successfully upgraded beacon Box to BoxV2 on address: ', beacon.address)); await boxV2Upgraded.deployed(); - console.log(boxV2Upgraded.signer); // wait some time before the next call await new Promise((resolve) => setTimeout(resolve, 2000)); const value = await boxV2Upgraded.retrieve(); diff --git a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts index 8757bc83b..be0e73b18 100644 --- a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts +++ b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts @@ -78,7 +78,7 @@ export class OpenzeppelinGenerator implements Generator { const { makeProposeUpgrade } = require('./platform/propose-upgrade'); const { makeGetDefaultApprovalProcess, - } = require('@openzeppelin/hardhat-upgrades/dist/get-default-approval-process'); + } = require('@openzeppelin/hardhat-upgrades/dist/platform/get-default-approval-process'); return { ...this.makeFunctions(true), From b589d78895ab6d7a4898bef954c9e7cb67c728d3 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Tue, 27 Aug 2024 14:19:33 +0200 Subject: [PATCH 05/11] fix: add verify for non zksync networks --- packages/hardhat-zksync-upgradable/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/hardhat-zksync-upgradable/src/index.ts b/packages/hardhat-zksync-upgradable/src/index.ts index 87c71b571..a9c931a68 100644 --- a/packages/hardhat-zksync-upgradable/src/index.ts +++ b/packages/hardhat-zksync-upgradable/src/index.ts @@ -101,6 +101,12 @@ subtask(TASK_COMPILE_SOLIDITY_COMPILE, async (args: RunCompilerArgs, hre, runSup }); subtask('verify:verify').setAction(async (args, hre, runSuper) => { + if (!hre.network.zksync) { + // eslint-disable-next-line @typescript-eslint/no-shadow + const { verify } = await import('@openzeppelin/hardhat-upgrades/dist/verify-proxy'); + return await verify(args, hre, runSuper); + } + const { verify } = await import('./verify/verify-proxy'); return await verify(args, hre, runSuper); }); From 25ec2c40c11447d4e6e63976e0d0dcc64d2e916a Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Tue, 27 Aug 2024 14:30:50 +0200 Subject: [PATCH 06/11] fix: add paymaster params for proxy and implementation deployment --- .../hardhat-zksync-upgradable/src/index.ts | 2 +- .../proxy-deployment/deploy-beacon-proxy.ts | 2 ++ .../src/proxy-deployment/deploy-impl.ts | 5 +++++ .../src/proxy-deployment/deploy-proxy-admin.ts | 18 ++++++++++++++---- .../src/proxy-deployment/deploy-proxy.ts | 6 +++++- .../src/utils/options.ts | 14 ++++++++++---- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/packages/hardhat-zksync-upgradable/src/index.ts b/packages/hardhat-zksync-upgradable/src/index.ts index a9c931a68..3f1d06d39 100644 --- a/packages/hardhat-zksync-upgradable/src/index.ts +++ b/packages/hardhat-zksync-upgradable/src/index.ts @@ -106,7 +106,7 @@ subtask('verify:verify').setAction(async (args, hre, runSuper) => { const { verify } = await import('@openzeppelin/hardhat-upgrades/dist/verify-proxy'); return await verify(args, hre, runSuper); } - + const { verify } = await import('./verify/verify-proxy'); return await verify(args, hre, runSuper); }); diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts index 340320518..4317dd4bc 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-beacon-proxy.ts @@ -141,6 +141,8 @@ async function deployBeaconProxy( ...(await deploy(beaconProxyFactory, beaconAddress, data, { customData: { salt: opts.salt, + paymasterParams: opts.paymasterParams, + ...opts.otherCustomData, }, })), }; diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-impl.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-impl.ts index a4de41061..3d8cf40d4 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-impl.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-impl.ts @@ -91,6 +91,11 @@ async function deployImpl 'salt' in opts ? (opts as UpgradeOptions).salt : (opts as UpgradeOptions).saltImpl, + paymasterParams: + 'paymasterParams' in opts + ? (opts as UpgradeOptions).paymasterParams + : (opts as UpgradeOptions).paymasterImplParams, + ...opts.otherCustomData, }, }, ], diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts index c53b57c3e..354149c1c 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts @@ -3,6 +3,7 @@ import * as zk from 'zksync-ethers'; import path from 'path'; import assert from 'assert'; import { ZkSyncArtifact } from '@matterlabs/hardhat-zksync-deploy/src/types'; +import { DeploymentType } from 'zksync-ethers/build/types'; import { DeployProxyAdminOptions } from '../utils/options'; import { PROXY_ADMIN_JSON } from '../constants'; import { fetchOrDeployAdmin } from '../core/impl-store'; @@ -13,8 +14,13 @@ export type DeployAdminFunction = (wallet?: zk.Wallet, opts?: DeployProxyAdminOp export function makeDeployProxyAdmin(hre: HardhatRuntimeEnvironment): any { return async function deployProxyAdmin(wallet: zk.Wallet, opts: DeployProxyAdminOptions = {}) { - const adminFactory = await getAdminFactory(hre, wallet); - return await fetchOrDeployAdmin(wallet.provider, () => deploy(adminFactory), opts); + const adminFactory = await getAdminFactory(hre, wallet, opts.deploymentType); + const customData = { + salt: opts.salt, + paymasterParams: opts.paymasterParams, + ...opts.otherCustomData, + }; + return await fetchOrDeployAdmin(wallet.provider, () => deploy(adminFactory, customData), opts); }; } @@ -26,7 +32,11 @@ export async function getAdminArtifact(hre: HardhatRuntimeEnvironment): Promise< return await import(proxyAdminPath); } -export async function getAdminFactory(hre: HardhatRuntimeEnvironment, wallet: zk.Wallet): Promise { +export async function getAdminFactory( + hre: HardhatRuntimeEnvironment, + wallet: zk.Wallet, + deploymentType?: DeploymentType, +): Promise { const proxyAdminContract = await getAdminArtifact(hre); - return new zk.ContractFactory(proxyAdminContract.abi, proxyAdminContract.bytecode, wallet); + return new zk.ContractFactory(proxyAdminContract.abi, proxyAdminContract.bytecode, wallet, deploymentType); } diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts index 653bf7f38..c8fe400e4 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy.ts @@ -134,6 +134,8 @@ async function deployProxy( const customDataProxy = { customData: { salt: opts.saltProxy, + paymasterParams: opts.paymasterProxyParams, + ...opts.otherCustomData, }, }; @@ -176,7 +178,9 @@ async function deployProxy( } case 'transparent': { - const adminAddress = await hre.zkUpgrades.deployProxyAdmin(wallet, {}); + const adminAddress = await hre.upgrades.deployProxyAdmin(wallet, { + paymasterParams: opts.paymasterProxyParams, + }); if (!quiet) { console.info(chalk.green(`Admin was deployed to ${adminAddress}`)); } diff --git a/packages/hardhat-zksync-upgradable/src/utils/options.ts b/packages/hardhat-zksync-upgradable/src/utils/options.ts index d0b354735..c505c917f 100644 --- a/packages/hardhat-zksync-upgradable/src/utils/options.ts +++ b/packages/hardhat-zksync-upgradable/src/utils/options.ts @@ -8,6 +8,7 @@ import { } from '@openzeppelin/upgrades-core'; import { DeploymentType } from 'zksync-ethers/src/types'; +import { BytesLike } from 'ethers'; import { LOCAL_SETUP_ZKSYNC_NETWORK } from '../constants'; export type StandaloneOptions = @@ -17,19 +18,24 @@ export type StandaloneOptions; + } & CustomDataOptions; -export type DeploymentTypesOptions = +export type CustomDataOptions = TRequiredSeperateForProxy extends true | undefined ? { + otherCustomData?: any; deploymentTypeImpl?: DeploymentType; deploymentTypeProxy?: DeploymentType; saltImpl?: string; saltProxy?: string; + paymasterImplParams?: BytesLike; + paymasterProxyParams?: BytesLike; } : { + otherCustomData?: any; deploymentType?: DeploymentType; salt?: string; + paymasterParams?: BytesLike; }; export type UpgradeOptions = @@ -53,10 +59,10 @@ interface Initializer { initializer?: string | false; } -export type DeployBeaconProxyOptions = ProxyKindOption & Initializer & DeploymentTypesOptions; +export type DeployBeaconProxyOptions = ProxyKindOption & Initializer & CustomDataOptions; export type DeployBeaconOptions = StandaloneOptions; export type DeployImplementationOptions = StandaloneOptions; -export type DeployProxyAdminOptions = DeployOpts; +export type DeployProxyAdminOptions = DeployOpts & CustomDataOptions; export type DeployProxyOptions = StandaloneOptions & Initializer; export type UpgradeBeaconOptions = UpgradeOptions; export type UpgradeProxyOptions = UpgradeOptions & { From abd0aaeb80063ed9b417af70538c01b067c8d4d5 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Wed, 28 Aug 2024 11:19:32 +0200 Subject: [PATCH 07/11] fix: change custom data for proxy admin and update readme file --- packages/hardhat-zksync-upgradable/README.md | 25 +++++++++++++++++++ .../proxy-deployment/deploy-proxy-admin.ts | 18 ++++++++----- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/hardhat-zksync-upgradable/README.md b/packages/hardhat-zksync-upgradable/README.md index 4256c5c3e..7210686eb 100644 --- a/packages/hardhat-zksync-upgradable/README.md +++ b/packages/hardhat-zksync-upgradable/README.md @@ -64,6 +64,19 @@ await hre.zkUpgrades.deployProxy(deployer.zkWallet, contract, [initializerFuncti Permissible values for the deployment type include `create`, `create2`, `createAccount`, and `create2Account`. If this parameter is omitted, the default value will be `create`. If the salt parameters are ommited, the default value will be `0x0000000000000000000000000000000000000000000000000000000000000000` +In the options section, paymaster parameters can be included for both proxy and implementation deployments. To do so, use: + - `paymasterProxyParams` + - `paymasterImplParams` + + ``` +await hre.zkUpgrades.deployProxy(deployer.zkWallet, contract, [initializerFunctionArguments], + { initializer: "initialize", + paymasterProxyParams: params, + paymasterImplParams: params, + } +); +``` + - **Deploying UUPS proxies** The UUPS proxy pattern is similar to the transparent proxy pattern, except that the upgrade is triggered via the logic contract instead of from the proxy contract. @@ -116,6 +129,18 @@ await box.deployed(); Permissible values for the deployment type include `create`, `create2`, `createAccount`, and `create2Account`. If this parameter is omitted, the default value will be `create`. If the salt parameters are ommited, the default value will be `0x0000000000000000000000000000000000000000000000000000000000000000` +In the options section, you can include paymaster parameters. To do so, use +`paymasterParams` argument. + +``` +await hre.zkUpgrades.deployBeacon(deployer.zkWallet, boxContract, { + paymasterParams: params +}); +const box = await hre.zkUpgrades.deployBeaconProxy(deployer.zkWallet, beacon, boxContract, [42], { + paymasterParams: params +}); +``` + - **Upgrading proxies** In order for a smart contract implementation to be upgradable, it has to follow specific [rules](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable). diff --git a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts index 354149c1c..3b43ba0d9 100644 --- a/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts +++ b/packages/hardhat-zksync-upgradable/src/proxy-deployment/deploy-proxy-admin.ts @@ -15,12 +15,18 @@ export type DeployAdminFunction = (wallet?: zk.Wallet, opts?: DeployProxyAdminOp export function makeDeployProxyAdmin(hre: HardhatRuntimeEnvironment): any { return async function deployProxyAdmin(wallet: zk.Wallet, opts: DeployProxyAdminOptions = {}) { const adminFactory = await getAdminFactory(hre, wallet, opts.deploymentType); - const customData = { - salt: opts.salt, - paymasterParams: opts.paymasterParams, - ...opts.otherCustomData, - }; - return await fetchOrDeployAdmin(wallet.provider, () => deploy(adminFactory, customData), opts); + return await fetchOrDeployAdmin( + wallet.provider, + () => + deploy(adminFactory, { + customData: { + salt: opts.salt, + paymasterParams: opts.paymasterParams, + ...opts.otherCustomData, + }, + }), + opts, + ); }; } From a63149a6d2a7e5aea2acc5b7c3f432821b4a3f4b Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Wed, 28 Aug 2024 13:30:17 +0200 Subject: [PATCH 08/11] fix: change hardhat-etherscan to hardhat-verify --- .../hardhat-zksync-upgradable/package.json | 8 +- .../hardhat-zksync-upgradable/src/index.ts | 4 +- .../utils/etherscan-api.ts | 90 +++ .../verify-proxy.ts | 642 ++++++++++++++++++ pnpm-lock.yaml | 6 + 5 files changed, 746 insertions(+), 4 deletions(-) create mode 100644 packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/utils/etherscan-api.ts create mode 100644 packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/verify-proxy.ts diff --git a/packages/hardhat-zksync-upgradable/package.json b/packages/hardhat-zksync-upgradable/package.json index d0cbfde6a..03f4733f5 100644 --- a/packages/hardhat-zksync-upgradable/package.json +++ b/packages/hardhat-zksync-upgradable/package.json @@ -52,9 +52,11 @@ "solidity-ast": "^0.4.56", "proper-lockfile": "^4.1.2", "semver": "^7.6.2", - "compare-versions": "^6.1.0" + "compare-versions": "^6.1.0", + "undici": "^6.19.2" }, "devDependencies": { + "@nomicfoundation/hardhat-verify": "^2.0.8", "@types/fs-extra": "^11.0.4", "@types/node": "^18.11.17", "@types/proper-lockfile": "^4.1.2", @@ -73,7 +75,9 @@ "typescript": "^5.3.0", "c8": "^9.0.1" }, - "peerDependencies": {}, + "peerDependencies": { + "@nomicfoundation/hardhat-verify": "^2.0.8" + }, "prettier": { "tabWidth": 4, "printWidth": 120, diff --git a/packages/hardhat-zksync-upgradable/src/index.ts b/packages/hardhat-zksync-upgradable/src/index.ts index a9c931a68..d358ac4d4 100644 --- a/packages/hardhat-zksync-upgradable/src/index.ts +++ b/packages/hardhat-zksync-upgradable/src/index.ts @@ -103,10 +103,10 @@ subtask(TASK_COMPILE_SOLIDITY_COMPILE, async (args: RunCompilerArgs, hre, runSup subtask('verify:verify').setAction(async (args, hre, runSuper) => { if (!hre.network.zksync) { // eslint-disable-next-line @typescript-eslint/no-shadow - const { verify } = await import('@openzeppelin/hardhat-upgrades/dist/verify-proxy'); + const { verify } = await import('./openzeppelin-hardhat-upgrades/verify-proxy'); return await verify(args, hre, runSuper); } - + const { verify } = await import('./verify/verify-proxy'); return await verify(args, hre, runSuper); }); diff --git a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/utils/etherscan-api.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/utils/etherscan-api.ts new file mode 100644 index 000000000..c39227326 --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/utils/etherscan-api.ts @@ -0,0 +1,90 @@ +import { UpgradesError } from '@openzeppelin/upgrades-core'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { request } from 'undici'; + +import debug from '@openzeppelin/hardhat-upgrades/dist/utils/debug'; +import { Etherscan } from '@nomicfoundation/hardhat-verify/etherscan'; + +/** + * Call the configured Etherscan API with the given parameters. + * + * @param etherscan Etherscan instance + * @param params The API parameters to call with + * @returns The Etherscan API response + */ +export async function callEtherscanApi(etherscan: Etherscan, params: any): Promise { + const parameters = new URLSearchParams({ ...params, apikey: etherscan.apiKey }); + + const response = await request(etherscan.apiUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: parameters.toString(), + }); + + if (!(response.statusCode >= 200 && response.statusCode <= 299)) { + const responseBodyText = await response.body.text(); + throw new UpgradesError( + `Etherscan API call failed with status ${response.statusCode}, response: ${responseBodyText}`, + ); + } + + const responseBodyJson = await response.body.json(); + debug('Etherscan response', JSON.stringify(responseBodyJson)); + + return responseBodyJson as EtherscanResponseBody; +} + +/** + * Gets an Etherscan instance based on Hardhat config. + * Throws an error if Etherscan API key is not present in config. + */ +export async function getEtherscanInstance(hre: HardhatRuntimeEnvironment): Promise { + const etherscanConfig: EtherscanConfig | undefined = (hre.config as any).etherscan; // This should never be undefined, but check just in case + const chainConfig = await Etherscan.getCurrentChainConfig( + hre.network.name, + hre.network.provider, + etherscanConfig?.customChains ?? [], + ); + + return Etherscan.fromChainConfig(etherscanConfig?.apiKey, chainConfig); +} + +/** + * Etherscan configuration for hardhat-verify. + */ +interface EtherscanConfig { + apiKey: string | Record; + customChains: any[]; +} + +/** + * The response body from an Etherscan API call. + */ +interface EtherscanResponseBody { + status: string; + message: string; + result: any; +} + +export const RESPONSE_OK = '1'; + +export async function verifyAndGetStatus( + params: { + contractAddress: string; + sourceCode: string; + contractName: string; + compilerVersion: string; + constructorArguments: string; + }, + etherscan: Etherscan, +) { + const response = await etherscan.verify( + params.contractAddress, + params.sourceCode, + params.contractName, + params.compilerVersion, + params.constructorArguments, + ); + return etherscan.getVerificationStatus(response.message); +} diff --git a/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/verify-proxy.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/verify-proxy.ts new file mode 100644 index 000000000..d3cad1328 --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/verify-proxy.ts @@ -0,0 +1,642 @@ +import { + getTransactionByHash, + getImplementationAddress, + getBeaconAddress, + getImplementationAddressFromBeacon, + UpgradesError, + getAdminAddress, + isTransparentOrUUPSProxy, + isBeacon, + isBeaconProxy, + isEmptySlot, +} from '@openzeppelin/upgrades-core'; +import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info.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 { keccak256 } from 'ethereumjs-util'; + +import debug from '@openzeppelin/hardhat-upgrades/dist/utils/debug'; +import { Etherscan } from '@nomicfoundation/hardhat-verify/etherscan'; +import { callEtherscanApi, getEtherscanInstance, RESPONSE_OK, verifyAndGetStatus } from './utils/etherscan-api'; + +/** + * Hardhat artifact for a precompiled contract + */ +interface ContractArtifact { + contractName: string; + sourceName: string; + abi: any; + bytecode: any; +} + +/** + * A contract artifact and the corresponding event that it logs during construction. + */ +interface VerifiableContractInfo { + artifact: ContractArtifact; + event: string; +} + +interface ErrorReport { + errors: string[]; + severity: 'error' | 'warn'; +} + +/** + * The proxy-related contracts and their corresponding events that may have been deployed the current version of this plugin. + */ +const verifiableContracts = { + erc1967proxy: { artifact: ERC1967Proxy, event: 'Upgraded(address)' }, + beaconProxy: { artifact: BeaconProxy, event: 'BeaconUpgraded(address)' }, + upgradeableBeacon: { artifact: UpgradeableBeacon, event: 'OwnershipTransferred(address,address)' }, + transparentUpgradeableProxy: { artifact: TransparentUpgradeableProxy, event: 'AdminChanged(address,address)' }, + proxyAdmin: { artifact: ProxyAdmin, event: 'OwnershipTransferred(address,address)' }, +}; + +/** + * Overrides hardhat-verify's verify:etherscan subtask to fully verify a proxy or beacon. + * + * Verifies the contract at an address. If the address is an ERC-1967 compatible proxy, verifies the proxy and associated proxy contracts, + * as well as the implementation. Otherwise, calls hardhat-verify's verify function directly. + * + * @param args Args to the hardhat-verify verify function + * @param hre + * @param runSuper The parent function which is expected to be hardhat-verify's verify function + * @returns + */ +export async function verify(args: any, hre: HardhatRuntimeEnvironment, runSuper: RunSuperFunction) { + if (!runSuper.isDefined) { + throw new UpgradesError( + 'The hardhat-verify plugin must be imported before the hardhat-upgrades plugin.', + () => + 'Import the plugins in the following order in hardhat.config.js:\n' + + ' require("@nomicfoundation/hardhat-verify");\n' + + ' require("@openzeppelin/hardhat-upgrades");\n' + + 'Or if you are using TypeScript, import the plugins in the following order in hardhat.config.ts:\n' + + ' import "@nomicfoundation/hardhat-verify";\n' + + ' import "@openzeppelin/hardhat-upgrades";\n', + ); + } + + const provider = hre.network.provider; + const proxyAddress = args.address; + const errorReport: ErrorReport = { + errors: [], + severity: 'error', + }; + + let proxy = true; + + if (await isTransparentOrUUPSProxy(provider, proxyAddress)) { + await fullVerifyTransparentOrUUPS(hre, proxyAddress, hardhatVerify, errorReport); + } else if (await isBeaconProxy(provider, proxyAddress)) { + await fullVerifyBeaconProxy(hre, proxyAddress, hardhatVerify, errorReport); + } else if (await isBeacon(provider, proxyAddress)) { + proxy = false; + const etherscan = await getEtherscanInstance(hre); + await fullVerifyBeacon(hre, proxyAddress, hardhatVerify, etherscan, errorReport); + } else { + // Doesn't look like a proxy, so just verify directly + return hardhatVerify(proxyAddress); + } + + if (errorReport.errors.length > 0) { + displayErrorReport(errorReport); + } else { + console.info(`\n${proxy ? 'Proxy' : 'Contract'} fully verified.`); + } + + async function hardhatVerify(address: string) { + return await runSuper({ ...args, address }); + } +} + +/** + * Throws or warns with a formatted summary of all of the verification errors that have been recorded. + * + * @param errorReport Accumulated verification errors + * @throws UpgradesError if errorReport.severity is 'error' + */ +function displayErrorReport(errorReport: ErrorReport) { + let summary = `\nVerification completed with the following ${ + errorReport.severity === 'error' ? 'errors' : 'warnings' + }.`; + for (let i = 0; i < errorReport.errors.length; i++) { + const error = errorReport.errors[i]; + summary += `\n\n${errorReport.severity === 'error' ? 'Error' : 'Warning'} ${i + 1}: ${error}`; + } + if (errorReport.severity === 'error') { + throw new UpgradesError(summary); + } else { + console.warn(summary); + } +} + +/** + * Log an error about the given contract's verification attempt, and save it so it can be summarized at the end. + * + * @param address The address that failed to verify + * @param contractType The type or name of the contract + * @param details The error details + * @param errorReport Accumulated verification errors + */ +function recordVerificationError(address: string, contractType: string, details: string, errorReport: ErrorReport) { + const message = `Failed to verify ${contractType} contract at ${address}: ${details}`; + recordError(message, errorReport); +} + +function recordError(message: string, errorReport: ErrorReport) { + console.error(message); + errorReport.errors.push(message); +} + +/** + * Indicates that the expected event topic was not found in the contract's logs according to the Etherscan API. + */ +class EventNotFound extends UpgradesError {} + +/** + * Indicates that the contract's bytecode does not match with the plugin's artifact. + */ +class BytecodeNotMatchArtifact extends Error { + public contractName: string; + constructor(message: string, contractName: string) { + super(message); + this.contractName = contractName; + } +} + +/** + * Fully verifies all contracts related to the given transparent or UUPS proxy address: implementation, admin (if any), and proxy. + * Also links the proxy to the implementation ABI on Etherscan. + * + * This function will determine whether the address is a transparent or UUPS proxy based on whether its creation bytecode matches with + * TransparentUpgradeableProxy or ERC1967Proxy. + * + * Note: this function does not use the admin slot to determine whether the proxy is transparent or UUPS, but will always verify + * the admin address as long as the admin storage slot has an address. + * + * @param hre + * @param proxyAddress The transparent or UUPS proxy address + * @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command + * @param errorReport Accumulated verification errors + */ +async function fullVerifyTransparentOrUUPS( + hre: HardhatRuntimeEnvironment, + proxyAddress: any, + hardhatVerify: (address: string) => Promise, + errorReport: ErrorReport, +) { + const provider = hre.network.provider; + const implAddress = await getImplementationAddress(provider, proxyAddress); + await verifyImplementation(hardhatVerify, implAddress, errorReport); + + const etherscan = await getEtherscanInstance(hre); + + await verifyTransparentOrUUPS(); + await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport); + // Either UUPS or Transparent proxy could have admin slot set, although typically this should only be for Transparent + await verifyAdmin(); + + async function verifyAdmin() { + 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, + ); + } 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.', + ); + } + } + } + } + + async function verifyTransparentOrUUPS() { + console.log(`Verifying proxy: ${proxyAddress}`); + await verifyWithArtifactOrFallback( + hre, + hardhatVerify, + etherscan, + proxyAddress, + [verifiableContracts.transparentUpgradeableProxy, verifiableContracts.erc1967proxy], + errorReport, + true, + ); + } +} + +/** + * Fully verifies all contracts related to the given beacon proxy address: implementation, beacon, and beacon proxy. + * Also links the proxy to the implementation ABI on Etherscan. + * + * @param hre + * @param proxyAddress The beacon proxy address + * @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command + * @param errorReport Accumulated verification errors + */ +async function fullVerifyBeaconProxy( + hre: HardhatRuntimeEnvironment, + proxyAddress: any, + hardhatVerify: (address: string) => Promise, + errorReport: ErrorReport, +) { + const provider = hre.network.provider; + const beaconAddress = await getBeaconAddress(provider, proxyAddress); + const implAddress = await getImplementationAddressFromBeacon(provider, beaconAddress); + const etherscan = await getEtherscanInstance(hre); + + await fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscan, errorReport); + await verifyBeaconProxy(); + await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport); + + async function verifyBeaconProxy() { + console.log(`Verifying beacon proxy: ${proxyAddress}`); + await verifyWithArtifactOrFallback( + hre, + hardhatVerify, + etherscan, + proxyAddress, + [verifiableContracts.beaconProxy], + errorReport, + true, + ); + } +} + +/** + * Verifies all contracts resulting from a beacon deployment: implementation, beacon + * + * @param hre + * @param beaconAddress The beacon address + * @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command + * @param etherscan Etherscan instance + * @param errorReport Accumulated verification errors + */ +async function fullVerifyBeacon( + hre: HardhatRuntimeEnvironment, + beaconAddress: any, + hardhatVerify: (address: string) => Promise, + etherscan: Etherscan, + errorReport: ErrorReport, +) { + const provider = hre.network.provider; + + const implAddress = await getImplementationAddressFromBeacon(provider, beaconAddress); + await verifyImplementation(hardhatVerify, implAddress, errorReport); + await verifyBeacon(); + + async function verifyBeacon() { + console.log(`Verifying beacon or beacon-like contract: ${beaconAddress}`); + await verifyWithArtifactOrFallback( + hre, + hardhatVerify, + etherscan, + beaconAddress, + [verifiableContracts.upgradeableBeacon], + errorReport, + true, + ); + } +} + +/** + * Runs hardhat-verify plugin's verify command on the given implementation address. + * + * @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command + * @param implAddress The implementation address + * @param errorReport Accumulated verification errors + */ +async function verifyImplementation( + hardhatVerify: (address: string) => Promise, + implAddress: string, + errorReport: ErrorReport, +) { + try { + console.log(`Verifying implementation: ${implAddress}`); + await hardhatVerify(implAddress); + } catch (e: any) { + if (e.message.toLowerCase().includes('already verified')) { + console.log(`Implementation ${implAddress} already verified.`); + } else { + recordVerificationError(implAddress, 'implementation', e.message, errorReport); + } + } +} + +/** + * Looks for any of the possible events (in array order) at the specified address using Etherscan API, + * and returns the corresponding VerifiableContractInfo and txHash for the first event found. + * + * @param etherscan Etherscan instance + * @param address The contract address for which to look for events + * @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. + */ +async function searchEvent(etherscan: Etherscan, address: string, possibleContractInfo: VerifiableContractInfo[]) { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < possibleContractInfo.length; i++) { + const contractInfo = possibleContractInfo[i]; + const txHash = await getContractCreationTxHash(address, contractInfo.event, etherscan); + if (txHash !== undefined) { + return { contractInfo, txHash }; + } + } + + 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.', + ); +} + +/** + * 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. + * @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. + */ +async function verifyWithArtifactOrFallback( + hre: HardhatRuntimeEnvironment, + hardhatVerify: (address: string) => Promise, + etherscan: Etherscan, + address: string, + possibleContractInfo: VerifiableContractInfo[], + errorReport: ErrorReport, + convertErrorsToWarningsOnFallbackSuccess: boolean, +) { + try { + await attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport); + return true; + } catch (origError: any) { + if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventNotFound) { + // Try falling back to regular hardhat verify in case the source code is available in the user's project. + try { + await hardhatVerify(address); + } catch (fallbackError: any) { + if (fallbackError.message.toLowerCase().includes('already verified')) { + console.log(`Contract at ${address} already verified.`); + } else { + // Fallback failed, so record both the original error and the fallback attempt, then return + if (origError instanceof BytecodeNotMatchArtifact) { + recordVerificationError(address, origError.contractName, origError.message, errorReport); + } else { + recordError(origError.message, errorReport); + } + + recordError( + `Failed to verify directly using hardhat verify: ${fallbackError.message}`, + errorReport, + ); + return; + } + } + + // Since the contract was able to be verified directly, we don't want the task to fail so we should convert earlier errors into warnings for other related contracts. + // For example, the user provided constructor arguments for the verify command will apply to all calls of the regular hardhat verify, + // so it is not possible to successfully verify both an impl and a proxy that uses the above fallback at the same time. + if (convertErrorsToWarningsOnFallbackSuccess) { + errorReport.severity = 'warn'; + } + } else { + throw origError; + } + } +} + +/** + * 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. + * + * Iterates through each element of possibleContractInfo to look for that element's event, until an event is found. + * + * @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 + * @throws {EventNotFound} 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( + hre: HardhatRuntimeEnvironment, + etherscan: Etherscan, + address: string, + possibleContractInfo: VerifiableContractInfo[], + errorReport: ErrorReport, +) { + const { contractInfo, txHash } = await searchEvent(etherscan, address, possibleContractInfo); + debug(`verifying contract ${contractInfo.artifact.contractName} at ${address}`); + + const tx = await getTransactionByHash(hre.network.provider, txHash); + if (tx === null) { + // This should not happen since the txHash came from the logged event itself + throw new UpgradesError(`The transaction hash ${txHash} from the contract's logs was not found on the network`); + } + + const constructorArguments = inferConstructorArgs(tx.input, contractInfo.artifact.bytecode); + if (constructorArguments === undefined) { + // The creation bytecode for the address does not match with the expected artifact. + // This may be because a different version of the contract was deployed compared to what is in the plugins. + throw new BytecodeNotMatchArtifact( + `Bytecode does not match with the current version of ${contractInfo.artifact.contractName} in the Hardhat Upgrades plugin.`, + contractInfo.artifact.contractName, + ); + } else { + await verifyContractWithConstructorArgs( + etherscan, + address, + contractInfo.artifact, + constructorArguments, + errorReport, + ); + } +} + +/** + * Verifies a contract using the given constructor args. + * + * @param etherscan Etherscan instance + * @param address The address of the contract to verify + * @param artifact The contract artifact to use for verification. + * @param constructorArguments The constructor arguments to use for verification. + */ +async function verifyContractWithConstructorArgs( + etherscan: Etherscan, + address: string, + artifact: ContractArtifact, + constructorArguments: string, + errorReport: ErrorReport, +) { + debug(`verifying contract ${address} with constructor args ${constructorArguments}`); + + const params = { + contractAddress: address, + sourceCode: JSON.stringify(artifactsBuildInfo.input), + contractName: `${artifact.sourceName}:${artifact.contractName}`, + compilerVersion: `v${artifactsBuildInfo.solcLongVersion}`, + constructorArguments, + }; + + try { + const status = await verifyAndGetStatus(params, etherscan); + + if (status.isSuccess()) { + console.log(`Successfully verified contract ${artifact.contractName} at ${address}.`); + } else { + recordVerificationError(address, artifact.contractName, status.message, errorReport); + } + } catch (e: any) { + if (e.message.toLowerCase().includes('already verified')) { + console.log(`Contract at ${address} already verified.`); + } else { + recordVerificationError(address, artifact.contractName, e.message, errorReport); + } + } +} + +/** + * 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 params = { + module: 'logs', + action: 'getLogs', + fromBlock: '0', + toBlock: 'latest', + address, + topic0: `0x${keccak256(Buffer.from(topic)).toString('hex')}`, + }; + + const responseBody = await callEtherscanApi(etherscan, params); + + if (responseBody.status === RESPONSE_OK) { + const result = responseBody.result; + return result[0].transactionHash; // get the txhash from the first instance of this event + } 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; + } else { + throw new UpgradesError( + `Failed to get logs for contract at address ${address}.`, + () => `Etherscan returned with message: ${responseBody.message}, reason: ${responseBody.result}`, + ); + } +} + +/** + * Calls the Etherscan API to link a proxy with its implementation ABI. + * + * @param etherscan Etherscan instance + * @param proxyAddress The proxy address + * @param implAddress The implementation address + */ +async function linkProxyWithImplementationAbi( + etherscan: Etherscan, + proxyAddress: string, + implAddress: string, + errorReport: ErrorReport, +) { + console.log(`Linking proxy ${proxyAddress} with implementation`); + const params = { + module: 'contract', + action: 'verifyproxycontract', + address: proxyAddress, + expectedimplementation: implAddress, + }; + let responseBody = await callEtherscanApi(etherscan, params); + + if (responseBody.status === RESPONSE_OK) { + // initial call was OK, but need to send a status request using the returned guid to get the actual verification status + const guid = responseBody.result; + responseBody = await checkProxyVerificationStatus(etherscan, guid); + + while (responseBody.result === 'Pending in queue') { + await delay(3000); + responseBody = await checkProxyVerificationStatus(etherscan, guid); + } + } + + if (responseBody.status === RESPONSE_OK) { + console.log('Successfully linked proxy to implementation.'); + } else { + recordError( + `Failed to link proxy ${proxyAddress} with its implementation. Reason: ${responseBody.result}`, + errorReport, + ); + } + + async function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} + +async function checkProxyVerificationStatus(etherscan: Etherscan, guid: string) { + const checkProxyVerificationParams = { + module: 'contract', + action: 'checkproxyverification', + apikey: etherscan.apiKey, + guid, + }; + return await callEtherscanApi(etherscan, checkProxyVerificationParams); +} + +/** + * Gets the constructor args from the given transaction input and creation code. + * + * @param txInput The transaction input that was used to deploy the contract. + * @param creationCode The contract creation code. + * @returns the encoded constructor args, or undefined if txInput does not start with the creationCode. + */ +function inferConstructorArgs(txInput: string, creationCode: string) { + if (txInput.startsWith(creationCode)) { + return txInput.substring(creationCode.length); + } else { + return undefined; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5caa83856..a0cdae95b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -554,10 +554,16 @@ importers: solidity-ast: specifier: ^0.4.56 version: 0.4.56 + undici: + specifier: ^6.19.2 + version: 6.19.2 zksync-ethers: specifier: ^5.8.0 version: 5.8.0(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) devDependencies: + '@nomicfoundation/hardhat-verify': + specifier: ^2.0.8 + version: 2.0.8(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@types/debug': specifier: ^4.1.12 version: 4.1.12 From 0f0d7a883191e4a7294db017fcd099caa3fcc2b6 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Wed, 28 Aug 2024 15:32:01 +0200 Subject: [PATCH 09/11] fix: change paymaster params to PaymasterParams from zksync-ethers --- packages/hardhat-zksync-upgradable/src/utils/options.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/hardhat-zksync-upgradable/src/utils/options.ts b/packages/hardhat-zksync-upgradable/src/utils/options.ts index c505c917f..9850c2176 100644 --- a/packages/hardhat-zksync-upgradable/src/utils/options.ts +++ b/packages/hardhat-zksync-upgradable/src/utils/options.ts @@ -8,7 +8,7 @@ import { } from '@openzeppelin/upgrades-core'; import { DeploymentType } from 'zksync-ethers/src/types'; -import { BytesLike } from 'ethers'; +import { PaymasterParams } from 'zksync-ethers/build/types'; import { LOCAL_SETUP_ZKSYNC_NETWORK } from '../constants'; export type StandaloneOptions = @@ -28,14 +28,14 @@ export type CustomDataOptions = From e70f4ff686580f509342ea8b6600aeb095daaf7c Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Mon, 16 Sep 2024 15:07:20 +0200 Subject: [PATCH 10/11] fix: override verify etherscan task for non zksync networks --- packages/hardhat-zksync-upgradable/src/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/hardhat-zksync-upgradable/src/index.ts b/packages/hardhat-zksync-upgradable/src/index.ts index d358ac4d4..b7eda33d3 100644 --- a/packages/hardhat-zksync-upgradable/src/index.ts +++ b/packages/hardhat-zksync-upgradable/src/index.ts @@ -18,6 +18,7 @@ import { } from './task-names'; import { ExtensionGenerator } from './generator'; +import { ZkSyncUpgradablePluginError } from './errors'; extendEnvironment((hre) => { const extesionGenerator = new ExtensionGenerator(hre); @@ -100,6 +101,18 @@ subtask(TASK_COMPILE_SOLIDITY_COMPILE, async (args: RunCompilerArgs, hre, runSup return { output, solcBuild }; }); +subtask('verify:etherscan').setAction(async (args, hre, runSuper) => { + if (!hre.network.zksync) { + // eslint-disable-next-line @typescript-eslint/no-shadow + const { verify } = await import('./openzeppelin-hardhat-upgrades/verify-proxy'); + return await verify(args, hre, runSuper); + } + + throw new ZkSyncUpgradablePluginError( + 'This task is only available for zkSync network, use `verify:verify` instead', + ); +}); + subtask('verify:verify').setAction(async (args, hre, runSuper) => { if (!hre.network.zksync) { // eslint-disable-next-line @typescript-eslint/no-shadow From 51649c8c0f03f7e627bec9a322cee51915d7ab0d Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Mon, 16 Sep 2024 19:24:54 +0200 Subject: [PATCH 11/11] chore: fix examples to use zksync plugins --- examples/upgradable-example-l1/hardhat.config.ts | 4 +++- examples/upgradable-example-l1/package.json | 2 +- examples/upgradable-example/hardhat.config.ts | 1 + examples/upgradable-example/package.json | 1 + pnpm-lock.yaml | 9 ++++++--- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/upgradable-example-l1/hardhat.config.ts b/examples/upgradable-example-l1/hardhat.config.ts index 5063be5af..a8e708f2e 100644 --- a/examples/upgradable-example-l1/hardhat.config.ts +++ b/examples/upgradable-example-l1/hardhat.config.ts @@ -1,5 +1,7 @@ +import '@matterlabs/hardhat-zksync-solc'; +import '@matterlabs/hardhat-zksync-deploy'; import '@matterlabs/hardhat-zksync-upgradable'; -import "@nomiclabs/hardhat-ethers" +import '@matterlabs/hardhat-zksync-ethers'; import { HardhatUserConfig } from 'hardhat/config'; diff --git a/examples/upgradable-example-l1/package.json b/examples/upgradable-example-l1/package.json index c0296bfc0..f2c316ea0 100644 --- a/examples/upgradable-example-l1/package.json +++ b/examples/upgradable-example-l1/package.json @@ -33,7 +33,7 @@ "@matterlabs/hardhat-zksync-deploy": "workspace:^", "@matterlabs/hardhat-zksync-solc": "^1.2.0", "@matterlabs/hardhat-zksync-upgradable": "workspace:^", - "@nomiclabs/hardhat-ethers": "^2.2.3", + "@matterlabs/hardhat-zksync-ethers": "workspace:^", "@openzeppelin/contracts-upgradeable": "^4.9.2", "chalk": "^4.1.2", "hardhat": "2.14.0", diff --git a/examples/upgradable-example/hardhat.config.ts b/examples/upgradable-example/hardhat.config.ts index 4041af584..e30e26260 100644 --- a/examples/upgradable-example/hardhat.config.ts +++ b/examples/upgradable-example/hardhat.config.ts @@ -1,6 +1,7 @@ import '@matterlabs/hardhat-zksync-solc'; import '@matterlabs/hardhat-zksync-deploy'; import '@matterlabs/hardhat-zksync-upgradable'; +import '@matterlabs/hardhat-zksync-ethers'; import { HardhatUserConfig } from 'hardhat/config'; diff --git a/examples/upgradable-example/package.json b/examples/upgradable-example/package.json index 73b521cad..4608d88f9 100644 --- a/examples/upgradable-example/package.json +++ b/examples/upgradable-example/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", + "@matterlabs/hardhat-zksync-ethers": "workspace:^", "@matterlabs/hardhat-zksync-solc": "^1.2.2", "@matterlabs/hardhat-zksync-upgradable": "workspace:^", "@openzeppelin/contracts-upgradeable": "^4.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 185a38f00..ced758390 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,6 +160,9 @@ importers: '@matterlabs/hardhat-zksync-deploy': specifier: workspace:^ version: link:../../packages/hardhat-zksync-deploy + '@matterlabs/hardhat-zksync-ethers': + specifier: workspace:^ + version: link:../../packages/hardhat-zksync-ethers '@matterlabs/hardhat-zksync-solc': specifier: ^1.2.2 version: 1.2.2(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) @@ -227,15 +230,15 @@ importers: '@matterlabs/hardhat-zksync-deploy': specifier: workspace:^ version: link:../../packages/hardhat-zksync-deploy + '@matterlabs/hardhat-zksync-ethers': + specifier: workspace:^ + version: link:../../packages/hardhat-zksync-ethers '@matterlabs/hardhat-zksync-solc': specifier: ^1.2.0 version: 1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@matterlabs/hardhat-zksync-upgradable': specifier: workspace:^ version: link:../../packages/hardhat-zksync-upgradable - '@nomiclabs/hardhat-ethers': - specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) '@openzeppelin/contracts-upgradeable': specifier: ^4.9.2 version: 4.9.6