diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41974ace2..6025a26f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,6 +63,18 @@ 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 + + - 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 pnpm hardhat run scripts/upgrade-factory.ts - name: Test zksync-ethers example 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/.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/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..a8e708f2e --- /dev/null +++ b/examples/upgradable-example-l1/hardhat.config.ts @@ -0,0 +1,39 @@ +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'; + +const config: HardhatUserConfig = { + zksolc: { + version: 'latest', + compilerSource: 'binary', + settings: { + optimizer: { + enabled: true, + }, + }, + }, + defaultNetwork:'hardhat', + networks: { + hardhat: { + zksync: false, + }, + eth: { + zksync: false, + url: 'http://0.0.0.0:8545', + accounts: ['0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'] + }, + 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..f2c316ea0 --- /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:^", + "@matterlabs/hardhat-zksync-ethers": "workspace:^", + "@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..64fae43dd --- /dev/null +++ b/examples/upgradable-example-l1/scripts/upgrade-box-beacon.ts @@ -0,0 +1,29 @@ +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('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(); + + // 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/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 754e549ae..4608d88f9 100644 --- a/examples/upgradable-example/package.json +++ b/examples/upgradable-example/package.json @@ -31,7 +31,8 @@ }, "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-ethers": "workspace:^", + "@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/package.json b/package.json index c7999ab44..6e9ea6d4c 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "typescript": "^5.3.0" }, "scripts": { - "build": "tsc --build packages/hardhat-zksync-deploy packages/hardhat-zksync-upgradable packages/hardhat-zksync-ethers packages/hardhat-zksync", - "watch": "tsc --build --watch packages/hardhat-zksync-deploy packages/hardhat-zksync-upgradable packages/hardhat-zksync-ethers packages/hardhat-zksync", + "build": "tsc --build packages/hardhat-zksync-deploy packages/hardhat-zksync-ethers packages/hardhat-zksync-upgradable packages/hardhat-zksync", + "watch": "tsc --build --watch packages/hardhat-zksync-deploy packages/hardhat-zksync-ethers packages/hardhat-zksync-upgradable packages/hardhat-zksync", "clean": "pnpm run --recursive clean", "lint": "pnpm run --recursive lint", "lint:fix": "pnpm run --recursive lint:fix", 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/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/package.json b/packages/hardhat-zksync-upgradable/package.json index 607e185a7..def799147 100644 --- a/packages/hardhat-zksync-upgradable/package.json +++ b/packages/hardhat-zksync-upgradable/package.json @@ -34,9 +34,13 @@ ], "dependencies": { "@matterlabs/hardhat-zksync-deploy": "workspace:^", - "@matterlabs/hardhat-zksync-solc": "^1.2.0", + "@matterlabs/hardhat-zksync-ethers": "workspace:^", + "@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", "chalk": "^4.1.2", "fs-extra": "^11.2.0", "dockerode": "^3.3.4", @@ -48,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", @@ -69,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/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..f3f00c156 --- /dev/null +++ b/packages/hardhat-zksync-upgradable/src/extension-generator.ts @@ -0,0 +1,61 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { lazyObject } from 'hardhat/plugins'; +import { makeUndefinedFunction, wrapMakeFunction } from './utils'; +import { HardhatUpgrades } from './interfaces'; +import { HardhatUpgradesOZ } from './openzeppelin-hardhat-upgrades/interfaces'; +import { Generator } from './generator'; + +export 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()); + } +} 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 36457a0d5..2e0880aeb 100644 --- a/packages/hardhat-zksync-upgradable/src/index.ts +++ b/packages/hardhat-zksync-upgradable/src/index.ts @@ -1,15 +1,12 @@ import '@matterlabs/hardhat-zksync-solc'; +import '@matterlabs/hardhat-zksync-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 { 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 +16,13 @@ import { TASK_UPGRADE_ZKSYNC_BEACON, TASK_UPGRADE_ZKSYNC_PROXY, } from './task-names'; -import { checkOpenzeppelinVersions, 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 './generator'; +import { ZkSyncUpgradablePluginError } from './errors'; +extendEnvironment((hre) => { + const extesionGenerator = new ExtensionGenerator(hre); + extesionGenerator.populatedExtension(); hre.config.solidity.compilers.forEach((compiler) => { extendCompilerOutputSelection(compiler); }); @@ -133,23 +101,25 @@ 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: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 + 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/interfaces.ts b/packages/hardhat-zksync-upgradable/src/interfaces.ts index d78435d7e..0fce03570 100644 --- a/packages/hardhat-zksync-upgradable/src/interfaces.ts +++ b/packages/hardhat-zksync-upgradable/src/interfaces.ts @@ -2,16 +2,21 @@ 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 { 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'; +import { UndefinedFunctionType } from './utils'; export type ValidateImplementationFunction = ( ImplFactory: zk.ContractFactory, @@ -19,12 +24,12 @@ export type ValidateImplementationFunction = ( ) => 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 +41,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/openzeppelin-hardhat-upgrades/extension-generator.ts b/packages/hardhat-zksync-upgradable/src/openzeppelin-hardhat-upgrades/extension-generator.ts new file mode 100644 index 000000000..be0e73b18 --- /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/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/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/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/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..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 @@ -16,105 +16,145 @@ 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, + paymasterParams: opts.paymasterParams, + ...opts.otherCustomData, + }, + })), + }; - 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-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..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 @@ -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,19 @@ 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); + return await fetchOrDeployAdmin( + wallet.provider, + () => + deploy(adminFactory, { + customData: { + salt: opts.salt, + paymasterParams: opts.paymasterParams, + ...opts.otherCustomData, + }, + }), + opts, + ); }; } @@ -26,7 +38,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 84dbd9220..c8fe400e4 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,183 @@ 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, + paymasterParams: opts.paymasterProxyParams, + ...opts.otherCustomData, + }, + }; - 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.upgrades.deployProxyAdmin(wallet, { + paymasterParams: opts.paymasterProxyParams, + }); + 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..e574558ea 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[1]; + 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..701c8baeb 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[1]; + 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..b8ce1f949 100644 --- a/packages/hardhat-zksync-upgradable/src/type-extensions.ts +++ b/packages/hardhat-zksync-upgradable/src/type-extensions.ts @@ -1,8 +1,25 @@ import 'hardhat/types/runtime'; 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: 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 0daec7455..df5053487 100644 --- a/packages/hardhat-zksync-upgradable/src/utils.ts +++ b/packages/hardhat-zksync-upgradable/src/utils.ts @@ -19,19 +19,46 @@ export async function getWallet(hre: HardhatRuntimeEnvironment, privateKeyOrInde return wallet; } -export function checkOpenzeppelinVersions(wrappedFunction: (...args: any) => T): (...args: any) => T { - return function (...args: any): T { - try { - if (!isOpenzeppelinContractsVersionValid()) { - throw new Error(OZ_CONTRACTS_VERISION_INCOMPATIBLE_ERROR); - } - } catch (e: any) { - console.warn(chalk.yellow(e.message)); - } +export function wrapMakeFunction( + hre: HardhatRuntimeEnvironment, + wrappedFunction: (...args: any) => T, +): (...args: any) => Promise { + return async function (...args: any): Promise { + checkOpenzeppelinVersion(); + + 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 @@ -52,3 +79,25 @@ 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; +} + +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/utils/options.ts b/packages/hardhat-zksync-upgradable/src/utils/options.ts index d0b354735..9850c2176 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 { PaymasterParams } from 'zksync-ethers/build/types'; 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?: PaymasterParams; + paymasterProxyParams?: PaymasterParams; } : { + otherCustomData?: any; deploymentType?: DeploymentType; salt?: string; + paymasterParams?: PaymasterParams; }; 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 & { 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/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 b5283ae35..ced758390 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 @@ -160,9 +160,82 @@ 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)) + '@matterlabs/hardhat-zksync-upgradable': + specifier: workspace:^ + version: link:../../packages/hardhat-zksync-upgradable + '@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 + + examples/upgradable-example-l1: + dependencies: + '@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.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)) + 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 @@ -292,8 +365,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 @@ -395,8 +468,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 @@ -585,12 +658,24 @@ importers: '@matterlabs/hardhat-zksync-deploy': specifier: workspace:^ version: link:../hardhat-zksync-deploy + '@matterlabs/hardhat-zksync-ethers': + specifier: workspace:^ + version: link:../hardhat-zksync-ethers '@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' + '@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 @@ -624,10 +709,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 @@ -682,6 +773,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'} @@ -935,13 +1039,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 @@ -1068,6 +1172,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} @@ -1078,6 +1188,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 @@ -1146,6 +1277,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} @@ -1362,6 +1497,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'} @@ -1461,6 +1599,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} @@ -1562,6 +1703,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} @@ -2156,6 +2300,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} @@ -2611,6 +2758,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'} @@ -2627,6 +2777,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} @@ -3266,6 +3419,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'} @@ -3721,6 +3878,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'} @@ -3925,6 +4085,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 @@ -4464,7 +4645,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 @@ -4479,7 +4660,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 @@ -4497,7 +4678,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 @@ -4519,7 +4700,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 @@ -4749,6 +4930,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 @@ -4757,6 +4954,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 @@ -4870,6 +5105,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 @@ -5122,6 +5361,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: {} @@ -5232,13 +5481,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: @@ -5351,6 +5604,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 @@ -6167,6 +6426,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: {} @@ -6674,6 +6935,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: @@ -6693,6 +6961,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + js-cookie@2.2.1: {} + js-sdsl@4.4.2: {} js-sha3@0.8.0: {} @@ -7353,6 +7623,8 @@ snapshots: retry@0.12.0: {} + retry@0.13.1: {} + reusify@1.0.4: {} rimraf@2.7.1: @@ -7853,6 +8125,8 @@ snapshots: undici@6.19.2: {} + unfetch@4.2.0: {} + universalify@0.1.2: {} universalify@2.0.1: {} @@ -8058,7 +8332,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