From 86bbbf3e95ea9390ab92641b1b11c2ad2dbc7e96 Mon Sep 17 00:00:00 2001 From: Ivan Litteri Date: Tue, 16 Jan 2024 10:09:35 -0300 Subject: [PATCH] Revert zk fmt --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- .github/ISSUE_TEMPLATE/feature_request.md | 4 +- .prettierrc.js | 28 +- docs/Overview.md | 52 +- l1-contracts/hardhat.config.ts | 186 +- l1-contracts/scripts/allow-list-manager.ts | 288 +-- l1-contracts/scripts/deploy-erc20.ts | 180 +- l1-contracts/scripts/deploy-testkit.ts | 102 +- l1-contracts/scripts/deploy-testnet-token.ts | 154 +- l1-contracts/scripts/deploy-weth-bridges.ts | 96 +- .../scripts/deploy-withdrawal-helpers.ts | 88 +- l1-contracts/scripts/deploy.ts | 180 +- l1-contracts/scripts/initialize-bridges.ts | 337 ++-- l1-contracts/scripts/initialize-governance.ts | 152 +- .../scripts/initialize-l1-allow-list.ts | 102 +- .../scripts/initialize-l2-weth-token.ts | 325 ++-- l1-contracts/scripts/initialize-validator.ts | 100 +- .../scripts/initialize-weth-bridges.ts | 181 +- l1-contracts/scripts/migrate-governance.ts | 448 +++-- l1-contracts/scripts/read-variable.ts | 542 +++--- l1-contracts/scripts/revert-reason.ts | 174 +- l1-contracts/scripts/token-info.ts | 64 +- l1-contracts/scripts/upgrades/upgrade-1.ts | 106 +- l1-contracts/scripts/upgrades/upgrade-2.ts | 250 ++- l1-contracts/scripts/upgrades/upgrade-3.ts | 236 ++- l1-contracts/scripts/upgrades/upgrade-4.ts | 278 +-- l1-contracts/scripts/upgrades/upgrade-5.ts | 274 +-- l1-contracts/scripts/upgrades/upgrade-6.ts | 340 ++-- l1-contracts/scripts/utils.ts | 225 ++- l1-contracts/scripts/verify.ts | 118 +- l1-contracts/src.ts/deploy-utils.ts | 102 +- l1-contracts/src.ts/deploy.ts | 859 +++++---- l1-contracts/src.ts/diamondCut.ts | 176 +- .../unit_tests/erc20-bridge-upgrade.fork.ts | 131 +- .../test/unit_tests/executor_proof.spec.ts | 112 +- .../test/unit_tests/governance_test.spec.ts | 178 +- .../unit_tests/l1_erc20_bridge_test.spec.ts | 469 +++-- .../unit_tests/l1_weth_bridge_test.spec.ts | 462 +++-- .../test/unit_tests/l2-upgrade.test.spec.ts | 1656 ++++++++--------- .../test/unit_tests/mailbox_test.spec.ts | 776 ++++---- .../test/unit_tests/merkle_test.spec.ts | 110 +- .../unit_tests/priority_queue_test.spec.ts | 256 +-- .../test/unit_tests/proxy_test.spec.ts | 334 ++-- .../transaction_validator_test.spec.ts | 416 ++--- l1-contracts/test/unit_tests/utils.ts | 258 ++- .../validator_timelock_test.spec.ts | 476 +++-- l1-contracts/test/unit_tests/verifier.spec.ts | 782 ++++---- .../test/unit_tests/zksync-upgrade.fork.ts | 340 ++-- l1-contracts/upgrade-system/facets.ts | 280 +-- l1-contracts/upgrade-system/index.ts | 32 +- l1-contracts/upgrade-system/utils.ts | 22 +- l1-contracts/upgrade-system/verifier.ts | 130 +- l2-contracts/hardhat.config.ts | 78 +- l2-contracts/src/deployForceDeployUpgrader.ts | 94 +- l2-contracts/src/deployL2Weth.ts | 249 ++- l2-contracts/src/deployTestnetPaymaster.ts | 70 +- l2-contracts/src/publish-bridge-preimages.ts | 100 +- l2-contracts/src/upgradeL2BridgeImpl.ts | 562 +++--- l2-contracts/src/utils.ts | 264 +-- l2-contracts/src/verify.ts | 60 +- l2-contracts/test/weth.test.ts | 240 +-- 61 files changed, 7752 insertions(+), 7936 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 163e439d5..ecc2e3dd8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,9 +1,9 @@ --- name: Scripts-Related Bug Report about: Use this template for reporting script-related bugs. For contract-related bugs, see our security policy. -title: '' +title: "" labels: bug -assignees: '' +assignees: "" --- ### 🐛 Script Bug Report diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d921e066c..f04164903 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,9 +1,9 @@ --- name: Feature request about: Use this template for requesting features -title: '' +title: "" labels: feat -assignees: '' +assignees: "" --- ### 🌟 Feature Request diff --git a/.prettierrc.js b/.prettierrc.js index 85572821d..020982998 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,16 +1,16 @@ module.exports = { - ...require('@matterlabs/prettier-config'), - plugins: ['prettier-plugin-solidity'], - overrides: [ - { - files: '*.sol', - options: { - bracketSpacing: false, - printWidth: 120, - singleQuote: false, - tabWidth: 4, - useTabs: false - } - } - ] + ...require("@matterlabs/prettier-config"), + plugins: ["prettier-plugin-solidity"], + overrides: [ + { + files: "*.sol", + options: { + bracketSpacing: false, + printWidth: 120, + singleQuote: false, + tabWidth: 4, + useTabs: false, + }, + }, + ], }; diff --git a/docs/Overview.md b/docs/Overview.md index 75872e19d..b8ed84042 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -20,9 +20,7 @@ See the [documentation](https://era.zksync.io/docs/dev/fundamentals/rollups.html addresses. - **Security council** - an address of the Gnosis multisig with the trusted owners that can decrease upgrade timelock. - **Validator/Operator** - a privileged address that can commit/verify/execute L2 batches. -- **L2 batch (or just batch)** - An aggregation of multiple L2 blocks. Note, that while the API operates on L2 blocks, - the prove system operates on batches, which represent a single proved VM execution, which typically contains multiple - L2 blocks. +- **L2 batch (or just batch)** - An aggregation of multiple L2 blocks. Note, that while the API operates on L2 blocks, the prove system operates on batches, which represent a single proved VM execution, which typically contains multiple L2 blocks. - **Facet** - implementation contract. The word comes from the EIP-2535. - **Gas** - a unit that measures the amount of computational effort required to execute specific operations on the zkSync Era network. @@ -46,9 +44,9 @@ even an upgrade system is a separate facet that can be replaced. One of the differences from the reference implementation is access freezability. Each of the facets has an associated parameter that indicates if it is possible to freeze access to the facet. Privileged actors can freeze the **diamond** -(not a specific facet!) and all facets with the marker `isFreezable` should be inaccessible until the governor or its -owner unfreezes the diamond. Note that it is a very dangerous thing since the diamond proxy can freeze the upgrade -system and then the diamond will be frozen forever. +(not a specific facet!) and all facets with the marker `isFreezable` should be inaccessible until the governor or its owner +unfreezes the diamond. Note that it is a very dangerous thing since the diamond proxy can freeze the upgrade system and then +the diamond will be frozen forever. #### DiamondInit @@ -61,28 +59,28 @@ Implementation detail - function returns a magic value just like it is designed #### GettersFacet Separate facet, whose only function is providing `view` and `pure` methods. It also implements -[diamond loupe](https://eips.ethereum.org/EIPS/eip-2535#diamond-loupe) which makes managing facets easier. This contract -must never be frozen. +[diamond loupe](https://eips.ethereum.org/EIPS/eip-2535#diamond-loupe) which makes managing facets easier. +This contract must never be frozen. #### AdminFacet Controls changing the privileged addresses such as governor and validators or one of the system parameters (L2 -bootloader bytecode hash, verifier address, verifier parameters, etc), and it also manages the freezing/unfreezing and -execution of upgrades in the diamond proxy. +bootloader bytecode hash, verifier address, verifier parameters, etc), and it also manages the freezing/unfreezing and execution of +upgrades in the diamond proxy. #### Governance -This contract manages operations (calls with preconditions) for governance tasks. The contract allows for operations to -be scheduled, executed, and canceled with appropriate permissions and delays. It is used for managing and coordinating -upgrades and changes in all zkSync Era governed contracts. +This contract manages operations (calls with preconditions) for governance tasks. The contract allows for operations to be scheduled, +executed, and canceled with appropriate permissions and delays. It is used for managing and coordinating upgrades and changes in all +zkSync Era governed contracts. Each upgrade consists of two steps: - Upgrade Proposal - The governor can schedule upgrades in two different manners: - - Fully transparent data. All implementation contracts and migration contracts are known to the community. The - governor must wait for the timelock to execute the upgrade. - - Shadow upgrade. The governor only shows the commitment for the upgrade. The upgrade can be executed only with - security council approval without timelock. + - Fully transparent data. All implementation contracts and migration contracts are known to the community. The governor must wait + for the timelock to execute the upgrade. + - Shadow upgrade. The governor only shows the commitment for the upgrade. The upgrade can be executed only with security council + approval without timelock. - Upgrade execution - perform the upgrade that was proposed. #### MailboxFacet @@ -120,7 +118,6 @@ function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Ad l2Address = address(uint160(l1Address) + offset); } } - ``` For most of the rollups the address aliasing needs to prevent cross-chain exploits that would otherwise be possible if @@ -179,7 +176,6 @@ enum SystemLogKey { NUMBER_OF_LAYER_1_TXS_KEY, EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY } - ``` When a batch is committed, we process L2 -> L1 system logs. Here are the invariants that are expected there: @@ -252,16 +248,15 @@ investigation and mitigation before resuming normal operations. It is a temporary solution to prevent any significant impact of the validator hot key leakage, while the network is in the Alpha stage. -This contract consists of four main functions `commitBatches`, `proveBatches`, `executeBatches`, and `revertBatches`, -that can be called only by the validator. +This contract consists of four main functions `commitBatches`, `proveBatches`, `executeBatches`, and `revertBatches`, that +can be called only by the validator. -When the validator calls `commitBatches`, the same calldata will be propogated to the zkSync contract (`DiamondProxy` -through `call` where it invokes the `ExecutorFacet` through `delegatecall`), and also a timestamp is assigned to these -batches to track the time these batches are commited by the validator to enforce a delay between committing and -execution of batches. Then, the validator can prove the already commited batches regardless of the mentioned timestamp, -and again the same calldata (related to the `proveBatches` function) will be propogated to the zkSync contract. After, -the `delay` is elapsed, the validator is allowed to call `executeBatches` to propogate the same calldata to zkSync -contract. +When the validator calls `commitBatches`, the same calldata will be propogated to the zkSync contract (`DiamondProxy` through +`call` where it invokes the `ExecutorFacet` through `delegatecall`), and also a timestamp is assigned to these batches to track +the time these batches are commited by the validator to enforce a delay between committing and execution of batches. Then, the +validator can prove the already commited batches regardless of the mentioned timestamp, and again the same calldata (related +to the `proveBatches` function) will be propogated to the zkSync contract. After, the `delay` is elapsed, the validator +is allowed to call `executeBatches` to propogate the same calldata to zkSync contract. #### Allowlist @@ -312,7 +307,6 @@ struct Deposit { bool depositLimitation; uint256 depositCap; } - ``` Currently, the limit is used only for blocking deposits of the specific token (turning on the limitation and setting the diff --git a/l1-contracts/hardhat.config.ts b/l1-contracts/hardhat.config.ts index eb3cd8cf6..a89ea4e69 100644 --- a/l1-contracts/hardhat.config.ts +++ b/l1-contracts/hardhat.config.ts @@ -1,121 +1,121 @@ -import '@nomiclabs/hardhat-ethers'; -import '@nomiclabs/hardhat-etherscan'; -import '@nomiclabs/hardhat-solpp'; -import '@nomiclabs/hardhat-waffle'; -import 'hardhat-contract-sizer'; -import 'hardhat-gas-reporter'; -import 'hardhat-typechain'; -import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from 'hardhat/builtin-tasks/task-names'; -import { task } from 'hardhat/config'; -import 'solidity-coverage'; -import { getNumberFromEnv } from './scripts/utils'; +import "@nomiclabs/hardhat-ethers"; +import "@nomiclabs/hardhat-etherscan"; +import "@nomiclabs/hardhat-solpp"; +import "@nomiclabs/hardhat-waffle"; +import "hardhat-contract-sizer"; +import "hardhat-gas-reporter"; +import "hardhat-typechain"; +import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; +import { task } from "hardhat/config"; +import "solidity-coverage"; +import { getNumberFromEnv } from "./scripts/utils"; // If no network is specified, use the default config if (!process.env.CHAIN_ETH_NETWORK) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - require('dotenv').config(); + // eslint-disable-next-line @typescript-eslint/no-var-requires + require("dotenv").config(); } // eslint-disable-next-line @typescript-eslint/no-var-requires -const systemParams = require('../SystemConfig.json'); +const systemParams = require("../SystemConfig.json"); -const PRIORITY_TX_MAX_GAS_LIMIT = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); -const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv('CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT'); +const PRIORITY_TX_MAX_GAS_LIMIT = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); +const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv("CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT"); const prodConfig = { - UPGRADE_NOTICE_PERIOD: 0, - // PRIORITY_EXPIRATION: 101, - // NOTE: Should be greater than 0, otherwise zero approvals will be enough to make an instant upgrade! - SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE: 1, - PRIORITY_TX_MAX_GAS_LIMIT, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - DUMMY_VERIFIER: false + UPGRADE_NOTICE_PERIOD: 0, + // PRIORITY_EXPIRATION: 101, + // NOTE: Should be greater than 0, otherwise zero approvals will be enough to make an instant upgrade! + SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE: 1, + PRIORITY_TX_MAX_GAS_LIMIT, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + DUMMY_VERIFIER: false, }; const testnetConfig = { - UPGRADE_NOTICE_PERIOD: 0, - // PRIORITY_EXPIRATION: 101, - // NOTE: Should be greater than 0, otherwise zero approvals will be enough to make an instant upgrade! - SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE: 1, - PRIORITY_TX_MAX_GAS_LIMIT, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - DUMMY_VERIFIER: true + UPGRADE_NOTICE_PERIOD: 0, + // PRIORITY_EXPIRATION: 101, + // NOTE: Should be greater than 0, otherwise zero approvals will be enough to make an instant upgrade! + SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE: 1, + PRIORITY_TX_MAX_GAS_LIMIT, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + DUMMY_VERIFIER: true, }; const testConfig = { - UPGRADE_NOTICE_PERIOD: 0, - PRIORITY_EXPIRATION: 101, - SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE: 2, - PRIORITY_TX_MAX_GAS_LIMIT, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - DUMMY_VERIFIER: true + UPGRADE_NOTICE_PERIOD: 0, + PRIORITY_EXPIRATION: 101, + SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE: 2, + PRIORITY_TX_MAX_GAS_LIMIT, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + DUMMY_VERIFIER: true, }; const localConfig = { - ...prodConfig, - DUMMY_VERIFIER: true + ...prodConfig, + DUMMY_VERIFIER: true, }; const contractDefs = { - sepolia: testnetConfig, - rinkeby: testnetConfig, - ropsten: testnetConfig, - goerli: testnetConfig, - mainnet: prodConfig, - test: testConfig, - localhost: localConfig + sepolia: testnetConfig, + rinkeby: testnetConfig, + ropsten: testnetConfig, + goerli: testnetConfig, + mainnet: prodConfig, + test: testConfig, + localhost: localConfig, }; export default { - defaultNetwork: 'env', - solidity: { - version: '0.8.20', - settings: { - optimizer: { - enabled: true, - runs: 9999999 - }, - outputSelection: { - '*': { - '*': ['storageLayout'] - } - } - } - }, - contractSizer: { - runOnCompile: false, - except: ['dev-contracts', 'zksync/upgrade-initializers', 'zksync/libraries', 'common/libraries'] - }, - paths: { - sources: './contracts' + defaultNetwork: "env", + solidity: { + version: "0.8.20", + settings: { + optimizer: { + enabled: true, + runs: 9999999, + }, + outputSelection: { + "*": { + "*": ["storageLayout"], + }, + }, }, - solpp: { - defs: (() => { - const defs = process.env.CONTRACT_TESTS ? contractDefs.test : contractDefs[process.env.CHAIN_ETH_NETWORK]; + }, + contractSizer: { + runOnCompile: false, + except: ["dev-contracts", "zksync/upgrade-initializers", "zksync/libraries", "common/libraries"], + }, + paths: { + sources: "./contracts", + }, + solpp: { + defs: (() => { + const defs = process.env.CONTRACT_TESTS ? contractDefs.test : contractDefs[process.env.CHAIN_ETH_NETWORK]; - return { - ...systemParams, - ...defs - }; - })() - }, - networks: { - env: { - url: process.env.ETH_CLIENT_WEB3_URL?.split(',')[0] - }, - hardhat: { - allowUnlimitedContractSize: false, - forking: { - url: 'https://eth-goerli.g.alchemy.com/v2/' + process.env.ALCHEMY_KEY, - enabled: process.env.TEST_CONTRACTS_FORK === '1' - } - } + return { + ...systemParams, + ...defs, + }; + })(), + }, + networks: { + env: { + url: process.env.ETH_CLIENT_WEB3_URL?.split(",")[0], }, - etherscan: { - apiKey: process.env.MISC_ETHERSCAN_API_KEY + hardhat: { + allowUnlimitedContractSize: false, + forking: { + url: "https://eth-goerli.g.alchemy.com/v2/" + process.env.ALCHEMY_KEY, + enabled: process.env.TEST_CONTRACTS_FORK === "1", + }, }, - gasReporter: { - enabled: true - } + }, + etherscan: { + apiKey: process.env.MISC_ETHERSCAN_API_KEY, + }, + gasReporter: { + enabled: true, + }, }; -task('solpp', 'Preprocess Solidity source files').setAction(async (_, hre) => - hre.run(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS) +task("solpp", "Preprocess Solidity source files").setAction(async (_, hre) => + hre.run(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS) ); diff --git a/l1-contracts/scripts/allow-list-manager.ts b/l1-contracts/scripts/allow-list-manager.ts index 10ac06f0b..b1962121a 100644 --- a/l1-contracts/scripts/allow-list-manager.ts +++ b/l1-contracts/scripts/allow-list-manager.ts @@ -1,189 +1,189 @@ -import { Command } from 'commander'; -import { Interface } from 'ethers/lib/utils'; -import * as hardhat from 'hardhat'; -import type { AccessMode, PermissionToCall } from './utils'; -import { getLowerCaseAddress, permissionToCallComparator, print } from './utils'; +import { Command } from "commander"; +import { Interface } from "ethers/lib/utils"; +import * as hardhat from "hardhat"; +import type { AccessMode, PermissionToCall } from "./utils"; +import { getLowerCaseAddress, permissionToCallComparator, print } from "./utils"; // Get the interfaces for all needed contracts -const allowList = new Interface(hardhat.artifacts.readArtifactSync('IAllowList').abi); -const zkSync = new Interface(hardhat.artifacts.readArtifactSync('IZkSync').abi); -const l1ERC20Bridge = new Interface(hardhat.artifacts.readArtifactSync('L1ERC20Bridge').abi); +const allowList = new Interface(hardhat.artifacts.readArtifactSync("IAllowList").abi); +const zkSync = new Interface(hardhat.artifacts.readArtifactSync("IZkSync").abi); +const l1ERC20Bridge = new Interface(hardhat.artifacts.readArtifactSync("L1ERC20Bridge").abi); -const ZKSYNC_MAINNET_ADDRESS = '0x32400084c286cf3e17e7b677ea9583e60a000324'; -const L1_ERC20_BRIDGE_MAINNET_ADDRESS = '0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063'; +const ZKSYNC_MAINNET_ADDRESS = "0x32400084c286cf3e17e7b677ea9583e60a000324"; +const L1_ERC20_BRIDGE_MAINNET_ADDRESS = "0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063"; const ALPHA_MAINNET_ALLOW_LIST = [ - { - target: ZKSYNC_MAINNET_ADDRESS, - functionName: 'requestL2Transaction' - }, - { - target: L1_ERC20_BRIDGE_MAINNET_ADDRESS, - functionName: 'deposit' - }, - { - target: L1_ERC20_BRIDGE_MAINNET_ADDRESS, - functionName: 'claimFailedDeposit' - }, - { - target: L1_ERC20_BRIDGE_MAINNET_ADDRESS, - functionName: 'finalizeWithdrawal' - } + { + target: ZKSYNC_MAINNET_ADDRESS, + functionName: "requestL2Transaction", + }, + { + target: L1_ERC20_BRIDGE_MAINNET_ADDRESS, + functionName: "deposit", + }, + { + target: L1_ERC20_BRIDGE_MAINNET_ADDRESS, + functionName: "claimFailedDeposit", + }, + { + target: L1_ERC20_BRIDGE_MAINNET_ADDRESS, + functionName: "finalizeWithdrawal", + }, ]; function functionSelector(functionName: string): string { - const selectors = new Array(0); + const selectors = new Array(0); - try { - selectors.push(zkSync.getSighash(zkSync.getFunction(functionName))); - } catch { - // ignore - } + try { + selectors.push(zkSync.getSighash(zkSync.getFunction(functionName))); + } catch { + // ignore + } - try { - selectors.push(l1ERC20Bridge.getSighash(l1ERC20Bridge.getFunction(functionName))); - } catch { - // ignore - } + try { + selectors.push(l1ERC20Bridge.getSighash(l1ERC20Bridge.getFunction(functionName))); + } catch { + // ignore + } - if (selectors.length == 0) { - throw `No selector found for the ${functionName} function`; - } + if (selectors.length == 0) { + throw `No selector found for the ${functionName} function`; + } - if (selectors.length > 1) { - throw `More than one selectors found for the ${functionName} function`; - } + if (selectors.length > 1) { + throw `More than one selectors found for the ${functionName} function`; + } - return selectors[0]; + return selectors[0]; } function setBatchPermissionToCall(parameters: Array) { - parameters.sort(permissionToCallComparator); - for (let i = 1; i < parameters.length; i++) { - if (permissionToCallComparator(parameters[i - 1], parameters[i]) === 0) { - throw new Error('Duplicates for the set batch permission to call method'); - } + parameters.sort(permissionToCallComparator); + for (let i = 1; i < parameters.length; i++) { + if (permissionToCallComparator(parameters[i - 1], parameters[i]) === 0) { + throw new Error("Duplicates for the set batch permission to call method"); } - // Extend parameters with the function selector, to check it manually - const extendedParameters = parameters.map((param) => - Object.assign(param, { functionSel: functionSelector(param.functionName) }) - ); - print('parameters', extendedParameters); - - const callers = extendedParameters.map((permissionToCall) => permissionToCall.caller); - const targets = extendedParameters.map((permissionToCall) => permissionToCall.target); - const functionSelectors = extendedParameters.map((permissionToCall) => permissionToCall.functionSel); - const enables = extendedParameters.map((permissionToCall) => permissionToCall.enable); - - const calldata = allowList.encodeFunctionData('setBatchPermissionToCall', [ - callers, - targets, - functionSelectors, - enables - ]); - print('setBatchPermissionToCall', calldata); + } + // Extend parameters with the function selector, to check it manually + const extendedParameters = parameters.map((param) => + Object.assign(param, { functionSel: functionSelector(param.functionName) }) + ); + print("parameters", extendedParameters); + + const callers = extendedParameters.map((permissionToCall) => permissionToCall.caller); + const targets = extendedParameters.map((permissionToCall) => permissionToCall.target); + const functionSelectors = extendedParameters.map((permissionToCall) => permissionToCall.functionSel); + const enables = extendedParameters.map((permissionToCall) => permissionToCall.enable); + + const calldata = allowList.encodeFunctionData("setBatchPermissionToCall", [ + callers, + targets, + functionSelectors, + enables, + ]); + print("setBatchPermissionToCall", calldata); } function setPermissionToCall(caller: string, target: string, functionName: string, enable: boolean) { - const functionSel = functionSelector(functionName); - print('parameters', { caller, target, functionName, functionSel, enable }); + const functionSel = functionSelector(functionName); + print("parameters", { caller, target, functionName, functionSel, enable }); - const calldata = allowList.encodeFunctionData('setPermissionToCall', [caller, target, functionSel, enable]); - print('setPermissionToCall', calldata); + const calldata = allowList.encodeFunctionData("setPermissionToCall", [caller, target, functionSel, enable]); + print("setPermissionToCall", calldata); } function setAccessMode(target: string, mode: number) { - print('parameters', { target, mode }); + print("parameters", { target, mode }); - const calldata = allowList.encodeFunctionData('setAccessMode', [target, mode]); - print('setAccessMode', calldata); + const calldata = allowList.encodeFunctionData("setAccessMode", [target, mode]); + print("setAccessMode", calldata); } function setBatchAccessMode(parameters: Array) { - parameters.sort((a, b) => getLowerCaseAddress(a.target).localeCompare(getLowerCaseAddress(b.target))); - for (let i = 1; i < parameters.length; i++) { - if (getLowerCaseAddress(parameters[i - 1].target) === getLowerCaseAddress(parameters[i].target)) { - throw new Error('Duplicated targets for the set batch access mode method'); - } + parameters.sort((a, b) => getLowerCaseAddress(a.target).localeCompare(getLowerCaseAddress(b.target))); + for (let i = 1; i < parameters.length; i++) { + if (getLowerCaseAddress(parameters[i - 1].target) === getLowerCaseAddress(parameters[i].target)) { + throw new Error("Duplicated targets for the set batch access mode method"); } - print('parameters', parameters); + } + print("parameters", parameters); - const targets = parameters.map((publicAccess) => publicAccess.target); - const modes = parameters.map((publicAccess) => publicAccess.mode); + const targets = parameters.map((publicAccess) => publicAccess.target); + const modes = parameters.map((publicAccess) => publicAccess.mode); - const calldata = allowList.encodeFunctionData('setBatchAccessMode', [targets, modes]); - print('setBatchAccessMode', calldata); + const calldata = allowList.encodeFunctionData("setBatchAccessMode", [targets, modes]); + print("setBatchAccessMode", calldata); } async function main() { - const program = new Command(); - - program.version('0.1.0').name('allow-list-manager'); - - const prepareCalldataProgram = program.command('prepare-calldata'); - - prepareCalldataProgram - .command('set-permission-to-call') - .requiredOption('--caller ') - .requiredOption('--target ') - .requiredOption('--function-name ') - .requiredOption('--enable ') - .action((cmd) => { - setPermissionToCall(cmd.caller, cmd.target, cmd.functionName, cmd.enable); - }); - - prepareCalldataProgram - .command('set-batch-permission-to-call ') - .action((permissionToCall: string) => { - const parameters: Array = JSON.parse(permissionToCall); - setBatchPermissionToCall(parameters); - }); - - prepareCalldataProgram - .command('set-access-mode') - .requiredOption('--target ') - .requiredOption('--mode ') - .action((cmd) => { - setAccessMode(cmd.target, cmd.mode); - }); - - prepareCalldataProgram.command('set-batch-access-mode ').action((publicAccess: string) => { - const parameters = JSON.parse(publicAccess); - setBatchAccessMode(parameters); - }); + const program = new Command(); - const alphaMainnet = program.command('alpha-mainnet'); + program.version("0.1.0").name("allow-list-manager"); - alphaMainnet.command('add ').action(async (addresses: string) => { - const parsedAddresses = JSON.parse(addresses); - const parameters: Array = new Array(0); - for (const caller of parsedAddresses) { - for (const permission of ALPHA_MAINNET_ALLOW_LIST) { - parameters.push({ caller, enable: true, ...permission }); - } - } + const prepareCalldataProgram = program.command("prepare-calldata"); - setBatchPermissionToCall(parameters); + prepareCalldataProgram + .command("set-permission-to-call") + .requiredOption("--caller ") + .requiredOption("--target ") + .requiredOption("--function-name ") + .requiredOption("--enable ") + .action((cmd) => { + setPermissionToCall(cmd.caller, cmd.target, cmd.functionName, cmd.enable); }); - alphaMainnet.command('remove ').action(async (addresses: string) => { - const parsedAddresses = JSON.parse(addresses); - const parameters: Array = new Array(0); - for (const caller of parsedAddresses) { - for (const permission of ALPHA_MAINNET_ALLOW_LIST) { - parameters.push({ caller, enable: false, ...permission }); - } - } + prepareCalldataProgram + .command("set-batch-permission-to-call ") + .action((permissionToCall: string) => { + const parameters: Array = JSON.parse(permissionToCall); + setBatchPermissionToCall(parameters); + }); - setBatchPermissionToCall(parameters); + prepareCalldataProgram + .command("set-access-mode") + .requiredOption("--target ") + .requiredOption("--mode ") + .action((cmd) => { + setAccessMode(cmd.target, cmd.mode); }); - await program.parseAsync(process.argv); + prepareCalldataProgram.command("set-batch-access-mode ").action((publicAccess: string) => { + const parameters = JSON.parse(publicAccess); + setBatchAccessMode(parameters); + }); + + const alphaMainnet = program.command("alpha-mainnet"); + + alphaMainnet.command("add ").action(async (addresses: string) => { + const parsedAddresses = JSON.parse(addresses); + const parameters: Array = new Array(0); + for (const caller of parsedAddresses) { + for (const permission of ALPHA_MAINNET_ALLOW_LIST) { + parameters.push({ caller, enable: true, ...permission }); + } + } + + setBatchPermissionToCall(parameters); + }); + + alphaMainnet.command("remove ").action(async (addresses: string) => { + const parsedAddresses = JSON.parse(addresses); + const parameters: Array = new Array(0); + for (const caller of parsedAddresses) { + for (const permission of ALPHA_MAINNET_ALLOW_LIST) { + parameters.push({ caller, enable: false, ...permission }); + } + } + + setBatchPermissionToCall(parameters); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/deploy-erc20.ts b/l1-contracts/scripts/deploy-erc20.ts index bc9addf95..39b65346f 100644 --- a/l1-contracts/scripts/deploy-erc20.ts +++ b/l1-contracts/scripts/deploy-erc20.ts @@ -1,112 +1,112 @@ -import * as hardhat from 'hardhat'; -import '@nomiclabs/hardhat-ethers'; -import { Command } from 'commander'; -import { Wallet } from 'ethers'; -import { parseEther } from 'ethers/lib/utils'; -import { web3Provider } from './utils'; -import * as fs from 'fs'; -import * as path from 'path'; +import * as hardhat from "hardhat"; +import "@nomiclabs/hardhat-ethers"; +import { Command } from "commander"; +import { Wallet } from "ethers"; +import { parseEther } from "ethers/lib/utils"; +import { web3Provider } from "./utils"; +import * as fs from "fs"; +import * as path from "path"; -const DEFAULT_ERC20 = 'TestnetERC20Token'; +const DEFAULT_ERC20 = "TestnetERC20Token"; -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); const provider = web3Provider(); type Token = { - address: string | null; - name: string; - symbol: string; - decimals: number; + address: string | null; + name: string; + symbol: string; + decimals: number; }; type TokenDescription = Token & { - implementation?: string; + implementation?: string; }; async function deployToken(token: TokenDescription, wallet: Wallet): Promise { - token.implementation = token.implementation || DEFAULT_ERC20; - const tokenFactory = await hardhat.ethers.getContractFactory(token.implementation, wallet); - const args = token.implementation !== 'WETH9' ? [token.name, token.symbol, token.decimals] : []; - const erc20 = await tokenFactory.deploy(...args, { gasLimit: 5000000 }); - await erc20.deployTransaction.wait(); - - if (token.implementation !== 'WETH9') { - await erc20.mint(wallet.address, parseEther('3000000000')); - } - for (let i = 0; i < 10; ++i) { - const testWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic as string, "m/44'/60'/0'/0/" + i).connect( - provider - ); - if (token.implementation !== 'WETH9') { - await erc20.mint(testWallet.address, parseEther('3000000000')); - } + token.implementation = token.implementation || DEFAULT_ERC20; + const tokenFactory = await hardhat.ethers.getContractFactory(token.implementation, wallet); + const args = token.implementation !== "WETH9" ? [token.name, token.symbol, token.decimals] : []; + const erc20 = await tokenFactory.deploy(...args, { gasLimit: 5000000 }); + await erc20.deployTransaction.wait(); + + if (token.implementation !== "WETH9") { + await erc20.mint(wallet.address, parseEther("3000000000")); + } + for (let i = 0; i < 10; ++i) { + const testWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic as string, "m/44'/60'/0'/0/" + i).connect( + provider + ); + if (token.implementation !== "WETH9") { + await erc20.mint(testWallet.address, parseEther("3000000000")); } + } - token.address = erc20.address; + token.address = erc20.address; - // Remove the unneeded field - if (token.implementation) { - delete token.implementation; - } + // Remove the unneeded field + if (token.implementation) { + delete token.implementation; + } - return token; + return token; } async function main() { - const program = new Command(); - - program.version('0.1.0').name('deploy-erc20').description('deploy testnet erc20 token'); - - program - .command('add') - .option('-n, --token-name ') - .option('-s, --symbol ') - .option('-d, --decimals ') - .option('-i --implementation ') - .description('Adds a new token with a given fields') - .action(async (cmd) => { - const token: TokenDescription = { - address: null, - name: cmd.tokenName, - symbol: cmd.symbol, - decimals: cmd.decimals, - implementation: cmd.implementation - }; - - const wallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); - - console.log(JSON.stringify(await deployToken(token, wallet), null, 2)); - }); - - program - .command('add-multi ') - .option('--private-key ') - .description('Adds a multiple tokens given in JSON format') - .action(async (tokens_json: string, cmd) => { - const tokens: Array = JSON.parse(tokens_json); - const result = []; - - const wallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); - - for (const token of tokens) { - result.push(await deployToken(token, wallet)); - } - - console.log(JSON.stringify(result, null, 2)); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("deploy-erc20").description("deploy testnet erc20 token"); + + program + .command("add") + .option("-n, --token-name ") + .option("-s, --symbol ") + .option("-d, --decimals ") + .option("-i --implementation ") + .description("Adds a new token with a given fields") + .action(async (cmd) => { + const token: TokenDescription = { + address: null, + name: cmd.tokenName, + symbol: cmd.symbol, + decimals: cmd.decimals, + implementation: cmd.implementation, + }; + + const wallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); + + console.log(JSON.stringify(await deployToken(token, wallet), null, 2)); + }); + + program + .command("add-multi ") + .option("--private-key ") + .description("Adds a multiple tokens given in JSON format") + .action(async (tokens_json: string, cmd) => { + const tokens: Array = JSON.parse(tokens_json); + const result = []; + + const wallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); + + for (const token of tokens) { + result.push(await deployToken(token, wallet)); + } + + console.log(JSON.stringify(result, null, 2)); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/deploy-testkit.ts b/l1-contracts/scripts/deploy-testkit.ts index 488ccd60d..4ad2aa6cf 100644 --- a/l1-contracts/scripts/deploy-testkit.ts +++ b/l1-contracts/scripts/deploy-testkit.ts @@ -1,71 +1,69 @@ -import * as hardhat from 'hardhat'; -import '@nomiclabs/hardhat-ethers'; -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { Deployer } from '../src.ts/deploy'; +import * as hardhat from "hardhat"; +import "@nomiclabs/hardhat-ethers"; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { Deployer } from "../src.ts/deploy"; -import * as fs from 'fs'; -import * as path from 'path'; -import { web3Provider } from './utils'; +import * as fs from "fs"; +import * as path from "path"; +import { web3Provider } from "./utils"; -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { - const program = new Command(); + const program = new Command(); - program.version('0.1.0').name('deploy').description('deploy testkit contracts'); + program.version("0.1.0").name("deploy").description("deploy testkit contracts"); - program - .requiredOption('--genesis-root ') - .requiredOption('--genesis-rollup-leaf-index ') - .action(async (cmd) => { - process.env.CONTRACTS_GENESIS_ROOT = cmd.genesisRoot; - process.env.CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX = cmd.genesisRollupLeafIndex; + program + .requiredOption("--genesis-root ") + .requiredOption("--genesis-rollup-leaf-index ") + .action(async (cmd) => { + process.env.CONTRACTS_GENESIS_ROOT = cmd.genesisRoot; + process.env.CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX = cmd.genesisRollupLeafIndex; - if (process.env.CHAIN_ETH_NETWORK !== 'test') { - console.error('This deploy script is only for localhost-test network'); - process.exit(1); - } + if (process.env.CHAIN_ETH_NETWORK !== "test") { + console.error("This deploy script is only for localhost-test network"); + process.exit(1); + } - const provider = web3Provider(); - provider.pollingInterval = 10; + const provider = web3Provider(); + provider.pollingInterval = 10; - const deployWallet = ethers.Wallet.fromMnemonic(ethTestConfig.test_mnemonic, "m/44'/60'/0'/0/0").connect( - provider - ); + const deployWallet = ethers.Wallet.fromMnemonic(ethTestConfig.test_mnemonic, "m/44'/60'/0'/0/0").connect( + provider + ); - const deployer = new Deployer({ deployWallet, verbose: true }); - await deployer.deployAll(); + const deployer = new Deployer({ deployWallet, verbose: true }); + await deployer.deployAll(); - const zkSyncContract = deployer.zkSyncContract(deployWallet); - await (await zkSyncContract.setValidator(deployWallet.address, true)).wait(); + const zkSyncContract = deployer.zkSyncContract(deployWallet); + await (await zkSyncContract.setValidator(deployWallet.address, true)).wait(); - const tokenFactory = await hardhat.ethers.getContractFactory('TestnetERC20Token', deployWallet); - const erc20 = await tokenFactory.deploy('Matter Labs Trial Token', 'MLTT', 18, { gasLimit: 5000000 }); + const tokenFactory = await hardhat.ethers.getContractFactory("TestnetERC20Token", deployWallet); + const erc20 = await tokenFactory.deploy("Matter Labs Trial Token", "MLTT", 18, { gasLimit: 5000000 }); - console.log(`CONTRACTS_TEST_ERC20=${erc20.address}`); + console.log(`CONTRACTS_TEST_ERC20=${erc20.address}`); - const failOnReceiveFactory = await hardhat.ethers.getContractFactory('FailOnReceive', deployWallet); - const failOnReceive = await failOnReceiveFactory.deploy({ - gasLimit: 5000000 - }); - console.log(`CONTRACTS_FAIL_ON_RECEIVE=${failOnReceive.address}`); + const failOnReceiveFactory = await hardhat.ethers.getContractFactory("FailOnReceive", deployWallet); + const failOnReceive = await failOnReceiveFactory.deploy({ + gasLimit: 5000000, + }); + console.log(`CONTRACTS_FAIL_ON_RECEIVE=${failOnReceive.address}`); - for (let i = 0; i < 10; ++i) { - const testWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic, "m/44'/60'/0'/0/" + i).connect( - provider - ); - await (await erc20.mint(testWallet.address, '0x4B3B4CA85A86C47A098A224000000000')).wait(); - } - }); + for (let i = 0; i < 10; ++i) { + const testWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic, "m/44'/60'/0'/0/" + i).connect(provider); + await (await erc20.mint(testWallet.address, "0x4B3B4CA85A86C47A098A224000000000")).wait(); + } + }); - await program.parseAsync(process.argv); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/deploy-testnet-token.ts b/l1-contracts/scripts/deploy-testnet-token.ts index 94cfca593..219b69c5b 100644 --- a/l1-contracts/scripts/deploy-testnet-token.ts +++ b/l1-contracts/scripts/deploy-testnet-token.ts @@ -1,94 +1,94 @@ -import '@nomiclabs/hardhat-ethers'; -import { ArgumentParser } from 'argparse'; -import { Wallet } from 'ethers'; -import * as fs from 'fs'; -import * as hardhat from 'hardhat'; -import * as path from 'path'; -import { web3Provider } from './utils'; +import "@nomiclabs/hardhat-ethers"; +import { ArgumentParser } from "argparse"; +import { Wallet } from "ethers"; +import * as fs from "fs"; +import * as hardhat from "hardhat"; +import * as path from "path"; +import { web3Provider } from "./utils"; // eslint-disable-next-line @typescript-eslint/no-var-requires const mainnetTokens = require(`${process.env.ZKSYNC_HOME}/etc/tokens/mainnet`); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { - const parser = new ArgumentParser({ - version: '0.1.0', - addHelp: true, - description: 'Deploy contracts and publish them on Etherscan' - }); - parser.addArgument('--publish', { - required: false, - action: 'storeTrue', - help: 'Only publish code for deployed tokens' - }); - parser.addArgument('--deployerPrivateKey', { required: false, help: 'Wallet used to deploy contracts' }); - const args = parser.parseArgs(process.argv.slice(2)); + const parser = new ArgumentParser({ + version: "0.1.0", + addHelp: true, + description: "Deploy contracts and publish them on Etherscan", + }); + parser.addArgument("--publish", { + required: false, + action: "storeTrue", + help: "Only publish code for deployed tokens", + }); + parser.addArgument("--deployerPrivateKey", { required: false, help: "Wallet used to deploy contracts" }); + const args = parser.parseArgs(process.argv.slice(2)); - const provider = web3Provider(); - const wallet = args.deployerPrivateKey - ? new Wallet(args.deployerPrivateKey, provider) - : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); + const provider = web3Provider(); + const wallet = args.deployerPrivateKey + ? new Wallet(args.deployerPrivateKey, provider) + : Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); - if (process.env.CHAIN_ETH_NETWORK === 'mainnet') { - throw new Error('Test ERC20 tokens should not be deployed to mainnet'); - } + if (process.env.CHAIN_ETH_NETWORK === "mainnet") { + throw new Error("Test ERC20 tokens should not be deployed to mainnet"); + } - if (args.publish) { - // TODO: restore after testnet (SMA-388) - // console.log('Publishing source code'); - // let verifiedOnce = false; - // const networkTokens = require(`${process.env.ZKSYNC_HOME}/etc/tokens/${process.env.ETH_NETWORK}`); - // for (const token of networkTokens) { - // if (verifiedOnce) { - // break; - // } - // try { - // console.log(`Publishing code for : ${token.symbol}, ${token.address}`); - // const constructorArgs = [ - // `${token.name} (${process.env.CHAIN_ETH_NETWORK})`, - // token.symbol, - // token.decimals - // ]; - // const rawArgs = encodeConstructorArgs(contractCode, constructorArgs); - // await publishSourceCodeToEtherscan(token.address, 'TestnetERC20Token', rawArgs, 'contracts/test'); - // verifiedOnce = true; - // } catch (e) { - // console.log('Error failed to verified code:', e); - // } - // } - // return; - } + if (args.publish) { + // TODO: restore after testnet (SMA-388) + // console.log('Publishing source code'); + // let verifiedOnce = false; + // const networkTokens = require(`${process.env.ZKSYNC_HOME}/etc/tokens/${process.env.ETH_NETWORK}`); + // for (const token of networkTokens) { + // if (verifiedOnce) { + // break; + // } + // try { + // console.log(`Publishing code for : ${token.symbol}, ${token.address}`); + // const constructorArgs = [ + // `${token.name} (${process.env.CHAIN_ETH_NETWORK})`, + // token.symbol, + // token.decimals + // ]; + // const rawArgs = encodeConstructorArgs(contractCode, constructorArgs); + // await publishSourceCodeToEtherscan(token.address, 'TestnetERC20Token', rawArgs, 'contracts/test'); + // verifiedOnce = true; + // } catch (e) { + // console.log('Error failed to verified code:', e); + // } + // } + // return; + } - const result = []; + const result = []; - for (const token of mainnetTokens) { - const constructorArgs = [ - `${token.name} (${process.env.CHAIN_ETH_NETWORK})`, - token.symbol, - token.decimals, - { gasLimit: 800000 } - ]; + for (const token of mainnetTokens) { + const constructorArgs = [ + `${token.name} (${process.env.CHAIN_ETH_NETWORK})`, + token.symbol, + token.decimals, + { gasLimit: 800000 }, + ]; - console.log(`Deploying testnet ERC20: ${constructorArgs.toString()}`); - const tokenFactory = await hardhat.ethers.getContractFactory('TestnetERC20Token', wallet); - const erc20 = await tokenFactory.deploy(...constructorArgs); + console.log(`Deploying testnet ERC20: ${constructorArgs.toString()}`); + const tokenFactory = await hardhat.ethers.getContractFactory("TestnetERC20Token", wallet); + const erc20 = await tokenFactory.deploy(...constructorArgs); - const testnetToken = token; - testnetToken.address = erc20.address; - result.push(testnetToken); - } + const testnetToken = token; + testnetToken.address = erc20.address; + result.push(testnetToken); + } - fs.writeFileSync( - `${process.env.ZKSYNC_HOME}/etc/tokens/${process.env.CHAIN_ETH_NETWORK}.json`, - JSON.stringify(result, null, 2) - ); + fs.writeFileSync( + `${process.env.ZKSYNC_HOME}/etc/tokens/${process.env.CHAIN_ETH_NETWORK}.json`, + JSON.stringify(result, null, 2) + ); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/deploy-weth-bridges.ts b/l1-contracts/scripts/deploy-weth-bridges.ts index 36abe6c07..edba17994 100644 --- a/l1-contracts/scripts/deploy-weth-bridges.ts +++ b/l1-contracts/scripts/deploy-weth-bridges.ts @@ -1,55 +1,55 @@ -import { Command } from 'commander'; -import { Wallet, ethers } from 'ethers'; -import { Deployer } from '../src.ts/deploy'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import * as fs from 'fs'; -import * as path from 'path'; -import { web3Provider } from './utils'; +import { Command } from "commander"; +import { Wallet, ethers } from "ethers"; +import { Deployer } from "../src.ts/deploy"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import * as fs from "fs"; +import * as path from "path"; +import { web3Provider } from "./utils"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { - const program = new Command(); - - program.version('0.1.0').name('deploy').description('deploy weth bridges'); - - program - .option('--private-key ') - .option('--gas-price ') - .option('--nonce ') - .option('--create2-salt ') - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/0" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); - - const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); - - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - await deployer.deployWethBridgeContracts(create2Salt, gasPrice); - }); - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("deploy").description("deploy weth bridges"); + + program + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .option("--create2-salt ") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/0" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); + + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + await deployer.deployWethBridgeContracts(create2Salt, gasPrice); + }); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/deploy-withdrawal-helpers.ts b/l1-contracts/scripts/deploy-withdrawal-helpers.ts index fdb9cfb4d..cb09bab77 100644 --- a/l1-contracts/scripts/deploy-withdrawal-helpers.ts +++ b/l1-contracts/scripts/deploy-withdrawal-helpers.ts @@ -1,55 +1,53 @@ // This script deploys the contracts required both for production and // for testing of the contracts required for the `withdrawal-helpers` library -import * as hardhat from 'hardhat'; -import '@nomiclabs/hardhat-ethers'; -import { ethers } from 'ethers'; -import * as fs from 'fs'; -import * as path from 'path'; -import { web3Provider } from './utils'; +import * as hardhat from "hardhat"; +import "@nomiclabs/hardhat-ethers"; +import { ethers } from "ethers"; +import * as fs from "fs"; +import * as path from "path"; +import { web3Provider } from "./utils"; -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { - try { - if (!['test', 'localhost'].includes(process.env.CHAIN_ETH_NETWORK)) { - console.error('This deploy script is only for localhost-test network'); - process.exit(1); - } - - const provider = web3Provider(); - provider.pollingInterval = 10; - - const deployWallet = ethers.Wallet.fromMnemonic(ethTestConfig.test_mnemonic, "m/44'/60'/0'/0/0").connect( - provider - ); - const multicallFactory = await hardhat.ethers.getContractFactory('Multicall', deployWallet); - const multicallContract = await multicallFactory.deploy({ - gasLimit: 5000000 - }); - - const revertReceiveFactory = await hardhat.ethers.getContractFactory('RevertReceiveAccount', deployWallet); - const revertReceiveAccount = await revertReceiveFactory.deploy({ - gasLimit: 5000000 - }); - - const outConfig = { - multicall_address: multicallContract.address, - revert_receive_address: revertReceiveAccount.address - }; - const outConfigPath = path.join(process.env.ZKSYNC_HOME, 'etc/test_config/volatile/withdrawal-helpers.json'); - fs.writeFileSync(outConfigPath, JSON.stringify(outConfig), { encoding: 'utf-8' }); - process.exit(0); - } catch (err) { - console.log(`Error: ${err}`); - process.exit(1); + try { + if (!["test", "localhost"].includes(process.env.CHAIN_ETH_NETWORK)) { + console.error("This deploy script is only for localhost-test network"); + process.exit(1); } + + const provider = web3Provider(); + provider.pollingInterval = 10; + + const deployWallet = ethers.Wallet.fromMnemonic(ethTestConfig.test_mnemonic, "m/44'/60'/0'/0/0").connect(provider); + const multicallFactory = await hardhat.ethers.getContractFactory("Multicall", deployWallet); + const multicallContract = await multicallFactory.deploy({ + gasLimit: 5000000, + }); + + const revertReceiveFactory = await hardhat.ethers.getContractFactory("RevertReceiveAccount", deployWallet); + const revertReceiveAccount = await revertReceiveFactory.deploy({ + gasLimit: 5000000, + }); + + const outConfig = { + multicall_address: multicallContract.address, + revert_receive_address: revertReceiveAccount.address, + }; + const outConfigPath = path.join(process.env.ZKSYNC_HOME, "etc/test_config/volatile/withdrawal-helpers.json"); + fs.writeFileSync(outConfigPath, JSON.stringify(outConfig), { encoding: "utf-8" }); + process.exit(0); + } catch (err) { + console.log(`Error: ${err}`); + process.exit(1); + } } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/deploy.ts b/l1-contracts/scripts/deploy.ts index 043fadbce..be2bad52c 100644 --- a/l1-contracts/scripts/deploy.ts +++ b/l1-contracts/scripts/deploy.ts @@ -1,99 +1,99 @@ -import { Command } from 'commander'; -import { Wallet, ethers } from 'ethers'; -import { Deployer } from '../src.ts/deploy'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import * as fs from 'fs'; -import * as path from 'path'; -import { web3Provider } from './utils'; +import { Command } from "commander"; +import { Wallet, ethers } from "ethers"; +import { Deployer } from "../src.ts/deploy"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import * as fs from "fs"; +import * as path from "path"; +import { web3Provider } from "./utils"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { - const program = new Command(); - - program.version('0.1.0').name('deploy').description('deploy L1 contracts'); - - program - .option('--private-key ') - .option('--gas-price ') - .option('--nonce ') - .option('--owner-address ') - .option('--create2-salt ') - .option('--diamond-upgrade-init ') - .option('--only-verifier') - .option('--validium-mode') - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; - console.log(`Using owner address: ${ownerAddress}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - let nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); - - const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); - - const deployer = new Deployer({ - deployWallet, - ownerAddress, - verbose: true - }); - - // Create2 factory already deployed on the public networks, only deploy it on local node - if (process.env.CHAIN_ETH_NETWORK === 'localhost') { - await deployer.deployCreate2Factory({ gasPrice, nonce }); - nonce++; - - await deployer.deployMulticall3(create2Salt, { gasPrice, nonce }); - nonce++; - } - - if (cmd.onlyVerifier) { - await deployer.deployVerifier(create2Salt, { gasPrice, nonce }); - return; - } - - // Deploy diamond upgrade init contract if needed - const diamondUpgradeContractVersion = cmd.diamondUpgradeInit || 1; - if (diamondUpgradeContractVersion) { - await deployer.deployDiamondUpgradeInit(create2Salt, diamondUpgradeContractVersion, { - gasPrice, - nonce - }); - nonce++; - } - - await deployer.deployDefaultUpgrade(create2Salt, { - gasPrice, - nonce - }); - nonce++; - - await deployer.deployGovernance(create2Salt, { gasPrice, nonce }); - await deployer.deployAllowList(create2Salt, { gasPrice, nonce: nonce + 1 }); - await deployer.deployZkSyncContract(create2Salt, gasPrice, nonce + 2, cmd.validiumMode); - await deployer.deployBridgeContracts(create2Salt, gasPrice); // Do not pass nonce, since it was increment after deploying zkSync contracts - await deployer.deployWethBridgeContracts(create2Salt, gasPrice); - await deployer.deployValidatorTimelock(create2Salt, { gasPrice }); + const program = new Command(); + + program.version("0.1.0").name("deploy").description("deploy L1 contracts"); + + program + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .option("--owner-address ") + .option("--create2-salt ") + .option("--diamond-upgrade-init ") + .option("--only-verifier") + .option('--validium-mode') + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; + console.log(`Using owner address: ${ownerAddress}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + let nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); + + const deployer = new Deployer({ + deployWallet, + ownerAddress, + verbose: true, + }); + + // Create2 factory already deployed on the public networks, only deploy it on local node + if (process.env.CHAIN_ETH_NETWORK === "localhost") { + await deployer.deployCreate2Factory({ gasPrice, nonce }); + nonce++; + + await deployer.deployMulticall3(create2Salt, { gasPrice, nonce }); + nonce++; + } + + if (cmd.onlyVerifier) { + await deployer.deployVerifier(create2Salt, { gasPrice, nonce }); + return; + } + + // Deploy diamond upgrade init contract if needed + const diamondUpgradeContractVersion = cmd.diamondUpgradeInit || 1; + if (diamondUpgradeContractVersion) { + await deployer.deployDiamondUpgradeInit(create2Salt, diamondUpgradeContractVersion, { + gasPrice, + nonce, }); + nonce++; + } + + await deployer.deployDefaultUpgrade(create2Salt, { + gasPrice, + nonce, + }); + nonce++; + + await deployer.deployGovernance(create2Salt, { gasPrice, nonce }); + await deployer.deployAllowList(create2Salt, { gasPrice, nonce: nonce + 1 }); + await deployer.deployZkSyncContract(create2Salt, gasPrice, nonce + 2, cmd.validiumMode); + await deployer.deployBridgeContracts(create2Salt, gasPrice); // Do not pass nonce, since it was increment after deploying zkSync contracts + await deployer.deployWethBridgeContracts(create2Salt, gasPrice); + await deployer.deployValidatorTimelock(create2Salt, { gasPrice }); + }); - await program.parseAsync(process.argv); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/initialize-bridges.ts b/l1-contracts/scripts/initialize-bridges.ts index a4bbf9547..457d8a886 100644 --- a/l1-contracts/scripts/initialize-bridges.ts +++ b/l1-contracts/scripts/initialize-bridges.ts @@ -1,198 +1,189 @@ -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import { Deployer } from '../src.ts/deploy'; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { Deployer } from "../src.ts/deploy"; import { - applyL1ToL2Alias, - computeL2Create2Address, - getNumberFromEnv, - hashL2Bytecode, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - web3Provider -} from './utils'; + applyL1ToL2Alias, + computeL2Create2Address, + getNumberFromEnv, + hashL2Bytecode, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + web3Provider, +} from "./utils"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from "fs"; +import * as path from "path"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); -const contractArtifactsPath = path.join( - process.env.ZKSYNC_HOME as string, - 'era-contracts-lambda/l2-contracts/artifacts-zk/' -); +const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "era-contracts-lambda/l2-contracts/artifacts-zk/"); -const l2BridgeArtifactsPath = path.join(contractArtifactsPath, 'cache-zk/solpp-generated-contracts/bridge/'); +const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "cache-zk/solpp-generated-contracts/bridge/"); const openzeppelinTransparentProxyArtifactsPath = path.join( - contractArtifactsPath, - '@openzeppelin/contracts/proxy/transparent/' + contractArtifactsPath, + "@openzeppelin/contracts/proxy/transparent/" ); -const openzeppelinBeaconProxyArtifactsPath = path.join(contractArtifactsPath, '@openzeppelin/contracts/proxy/beacon'); +const openzeppelinBeaconProxyArtifactsPath = path.join(contractArtifactsPath, "@openzeppelin/contracts/proxy/beacon"); function readBytecode(path: string, fileName: string) { - return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: 'utf-8' })).bytecode; + return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).bytecode; } function readInterface(path: string, fileName: string) { - const abi = JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: 'utf-8' })).abi; - return new ethers.utils.Interface(abi); + const abi = JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).abi; + return new ethers.utils.Interface(abi); } const L2_ERC20_BRIDGE_PROXY_BYTECODE = readBytecode( - openzeppelinTransparentProxyArtifactsPath, - 'TransparentUpgradeableProxy' + openzeppelinTransparentProxyArtifactsPath, + "TransparentUpgradeableProxy" ); -const L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, 'L2ERC20Bridge'); -const L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, 'L2StandardERC20'); -const L2_STANDARD_ERC20_PROXY_BYTECODE = readBytecode(openzeppelinBeaconProxyArtifactsPath, 'BeaconProxy'); +const L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2ERC20Bridge"); +const L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2StandardERC20"); +const L2_STANDARD_ERC20_PROXY_BYTECODE = readBytecode(openzeppelinBeaconProxyArtifactsPath, "BeaconProxy"); const L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE = readBytecode( - openzeppelinBeaconProxyArtifactsPath, - 'UpgradeableBeacon' + openzeppelinBeaconProxyArtifactsPath, + "UpgradeableBeacon" ); -const L2_ERC20_BRIDGE_INTERFACE = readInterface(l2BridgeArtifactsPath, 'L2ERC20Bridge'); -const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv('CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT'); +const L2_ERC20_BRIDGE_INTERFACE = readInterface(l2BridgeArtifactsPath, "L2ERC20Bridge"); +const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv("CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT"); async function main() { - const program = new Command(); - - program.version('0.1.0').name('initialize-bridges'); - - program - .option('--private-key ') - .option('--gas-price ') - .option('--nonce ') - .option('--erc20-bridge ') - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/0" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); - - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - const erc20Bridge = cmd.erc20Bridge - ? deployer.defaultERC20Bridge(deployWallet).attach(cmd.erc20Bridge) - : deployer.defaultERC20Bridge(deployWallet); - - const priorityTxMaxGasLimit = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); - const l1GovernorAddress = await zkSync.getGovernor(); - // Check whether governor is a smart contract on L1 to apply alias if needed. - const l1GovernorCodeSize = ethers.utils.hexDataLength( - await deployWallet.provider.getCode(l1GovernorAddress) - ); - const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); - const abiCoder = new ethers.utils.AbiCoder(); - - const l2ERC20BridgeImplAddr = computeL2Create2Address( - applyL1ToL2Alias(erc20Bridge.address), - L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE, - '0x', - ethers.constants.HashZero - ); - - const proxyInitializationParams = L2_ERC20_BRIDGE_INTERFACE.encodeFunctionData('initialize', [ - erc20Bridge.address, - hashL2Bytecode(L2_STANDARD_ERC20_PROXY_BYTECODE), - l2GovernorAddress - ]); - const l2ERC20BridgeProxyAddr = computeL2Create2Address( - applyL1ToL2Alias(erc20Bridge.address), - L2_ERC20_BRIDGE_PROXY_BYTECODE, - ethers.utils.arrayify( - abiCoder.encode( - ['address', 'address', 'bytes'], - [l2ERC20BridgeImplAddr, l2GovernorAddress, proxyInitializationParams] - ) - ), - ethers.constants.HashZero - ); - - const l2StandardToken = computeL2Create2Address( - l2ERC20BridgeProxyAddr, - L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE, - '0x', - ethers.constants.HashZero - ); - const l2TokenFactoryAddr = computeL2Create2Address( - l2ERC20BridgeProxyAddr, - L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, - ethers.utils.arrayify(abiCoder.encode(['address'], [l2StandardToken])), - ethers.constants.HashZero - ); - - // There will be two deployments done during the initial initialization - const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( - gasPrice, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - - const requiredValueToPublishBytecodes = await zkSync.l2TransactionBaseCost( - gasPrice, - priorityTxMaxGasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - - const independentInitialization = [ - zkSync.requestL2Transaction( - ethers.constants.AddressZero, - 0, - '0x', - priorityTxMaxGasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE], - deployWallet.address, - { gasPrice, nonce, value: requiredValueToPublishBytecodes } - ), - erc20Bridge.initialize( - [ - L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE, - L2_ERC20_BRIDGE_PROXY_BYTECODE, - L2_STANDARD_ERC20_PROXY_BYTECODE - ], - l2TokenFactoryAddr, - l2GovernorAddress, - requiredValueToInitializeBridge, - requiredValueToInitializeBridge, - { - gasPrice, - nonce: nonce + 1, - value: requiredValueToInitializeBridge.mul(2) - } - ) - ]; - - const txs = await Promise.all(independentInitialization); - for (const tx of txs) { - console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); - } - const receipts = await Promise.all(txs.map((tx) => tx.wait(2))); - - console.log(`ERC20 bridge initialized, gasUsed: ${receipts[1].gasUsed.toString()}`); - console.log(`CONTRACTS_L2_ERC20_BRIDGE_ADDR=${await erc20Bridge.l2Bridge()}`); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("initialize-bridges"); + + program + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .option("--erc20-bridge ") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/0" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + const zkSync = deployer.zkSyncContract(deployWallet); + const erc20Bridge = cmd.erc20Bridge + ? deployer.defaultERC20Bridge(deployWallet).attach(cmd.erc20Bridge) + : deployer.defaultERC20Bridge(deployWallet); + + const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); + const l1GovernorAddress = await zkSync.getGovernor(); + // Check whether governor is a smart contract on L1 to apply alias if needed. + const l1GovernorCodeSize = ethers.utils.hexDataLength(await deployWallet.provider.getCode(l1GovernorAddress)); + const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); + const abiCoder = new ethers.utils.AbiCoder(); + + const l2ERC20BridgeImplAddr = computeL2Create2Address( + applyL1ToL2Alias(erc20Bridge.address), + L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE, + "0x", + ethers.constants.HashZero + ); + + const proxyInitializationParams = L2_ERC20_BRIDGE_INTERFACE.encodeFunctionData("initialize", [ + erc20Bridge.address, + hashL2Bytecode(L2_STANDARD_ERC20_PROXY_BYTECODE), + l2GovernorAddress, + ]); + const l2ERC20BridgeProxyAddr = computeL2Create2Address( + applyL1ToL2Alias(erc20Bridge.address), + L2_ERC20_BRIDGE_PROXY_BYTECODE, + ethers.utils.arrayify( + abiCoder.encode( + ["address", "address", "bytes"], + [l2ERC20BridgeImplAddr, l2GovernorAddress, proxyInitializationParams] + ) + ), + ethers.constants.HashZero + ); + + const l2StandardToken = computeL2Create2Address( + l2ERC20BridgeProxyAddr, + L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE, + "0x", + ethers.constants.HashZero + ); + const l2TokenFactoryAddr = computeL2Create2Address( + l2ERC20BridgeProxyAddr, + L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, + ethers.utils.arrayify(abiCoder.encode(["address"], [l2StandardToken])), + ethers.constants.HashZero + ); + + // There will be two deployments done during the initial initialization + const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( + gasPrice, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + const requiredValueToPublishBytecodes = await zkSync.l2TransactionBaseCost( + gasPrice, + priorityTxMaxGasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + const independentInitialization = [ + zkSync.requestL2Transaction( + ethers.constants.AddressZero, + 0, + "0x", + priorityTxMaxGasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + [L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE], + deployWallet.address, + { gasPrice, nonce, value: requiredValueToPublishBytecodes } + ), + erc20Bridge.initialize( + [L2_ERC20_BRIDGE_IMPLEMENTATION_BYTECODE, L2_ERC20_BRIDGE_PROXY_BYTECODE, L2_STANDARD_ERC20_PROXY_BYTECODE], + l2TokenFactoryAddr, + l2GovernorAddress, + requiredValueToInitializeBridge, + requiredValueToInitializeBridge, + { + gasPrice, + nonce: nonce + 1, + value: requiredValueToInitializeBridge.mul(2), + } + ), + ]; + + const txs = await Promise.all(independentInitialization); + for (const tx of txs) { + console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); + } + const receipts = await Promise.all(txs.map((tx) => tx.wait(2))); + + console.log(`ERC20 bridge initialized, gasUsed: ${receipts[1].gasUsed.toString()}`); + console.log(`CONTRACTS_L2_ERC20_BRIDGE_ADDR=${await erc20Bridge.l2Bridge()}`); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/initialize-governance.ts b/l1-contracts/scripts/initialize-governance.ts index 6fb38bc79..f382fb0f3 100644 --- a/l1-contracts/scripts/initialize-governance.ts +++ b/l1-contracts/scripts/initialize-governance.ts @@ -1,84 +1,84 @@ -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { Deployer } from '../src.ts/deploy'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import { web3Provider } from './utils'; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { Deployer } from "../src.ts/deploy"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { web3Provider } from "./utils"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from "fs"; +import * as path from "path"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { - const program = new Command(); - - program.version('0.1.0').name('initialize-governance'); - - program - .option('--private-key ') - .option('--owner-address ') - .option('--gas-price ') - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; - - const deployer = new Deployer({ - deployWallet, - ownerAddress, - verbose: true - }); - - const governance = deployer.governanceContract(deployWallet); - const zkSync = deployer.zkSyncContract(deployWallet); - - const erc20Bridge = deployer.transparentUpgradableProxyContract( - deployer.addresses.Bridges.ERC20BridgeProxy, - deployWallet - ); - const wethBridge = deployer.transparentUpgradableProxyContract( - deployer.addresses.Bridges.WethBridgeProxy, - deployWallet - ); - - await (await erc20Bridge.changeAdmin(governance.address)).wait(); - await (await wethBridge.changeAdmin(governance.address)).wait(); - - await (await zkSync.setPendingGovernor(governance.address)).wait(); - - const call = { - target: zkSync.address, - value: 0, - data: zkSync.interface.encodeFunctionData('acceptGovernor') - }; - - const operation = { - calls: [call], - predecessor: ethers.constants.HashZero, - salt: ethers.constants.HashZero - }; - - await (await governance.scheduleTransparent(operation, 0)).wait(); - await (await governance.execute(operation)).wait(); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("initialize-governance"); + + program + .option("--private-key ") + .option("--owner-address ") + .option("--gas-price ") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; + + const deployer = new Deployer({ + deployWallet, + ownerAddress, + verbose: true, + }); + + const governance = deployer.governanceContract(deployWallet); + const zkSync = deployer.zkSyncContract(deployWallet); + + const erc20Bridge = deployer.transparentUpgradableProxyContract( + deployer.addresses.Bridges.ERC20BridgeProxy, + deployWallet + ); + const wethBridge = deployer.transparentUpgradableProxyContract( + deployer.addresses.Bridges.WethBridgeProxy, + deployWallet + ); + + await (await erc20Bridge.changeAdmin(governance.address)).wait(); + await (await wethBridge.changeAdmin(governance.address)).wait(); + + await (await zkSync.setPendingGovernor(governance.address)).wait(); + + const call = { + target: zkSync.address, + value: 0, + data: zkSync.interface.encodeFunctionData("acceptGovernor"), + }; + + const operation = { + calls: [call], + predecessor: ethers.constants.HashZero, + salt: ethers.constants.HashZero, + }; + + await (await governance.scheduleTransparent(operation, 0)).wait(); + await (await governance.execute(operation)).wait(); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/initialize-l1-allow-list.ts b/l1-contracts/scripts/initialize-l1-allow-list.ts index 5bc82a2bd..772885235 100644 --- a/l1-contracts/scripts/initialize-l1-allow-list.ts +++ b/l1-contracts/scripts/initialize-l1-allow-list.ts @@ -1,61 +1,61 @@ -import { Command } from 'commander'; -import { Wallet } from 'ethers'; -import { Deployer } from '../src.ts/deploy'; -import * as fs from 'fs'; -import * as path from 'path'; -import { web3Provider } from './utils'; +import { Command } from "commander"; +import { Wallet } from "ethers"; +import { Deployer } from "../src.ts/deploy"; +import * as fs from "fs"; +import * as path from "path"; +import { web3Provider } from "./utils"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); export enum AccessMode { - Closed = 0, - SpecialAccessOnly = 1, - Public = 2 + Closed = 0, + SpecialAccessOnly = 1, + Public = 2, } async function main() { - const program = new Command(); - - program.version('0.1.0').name('initialize-l1-allow-list'); - - program - .option('--private-key ') - .option('--nonce ') - .action(async (cmd) => { - const wallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using wallet: ${wallet.address}`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await wallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); - - const deployer = new Deployer({ deployWallet: wallet }); - - const allowListContract = deployer.l1AllowList(wallet); - const tx = await allowListContract.setBatchAccessMode( - [ - deployer.addresses.ZkSync.DiamondProxy, - deployer.addresses.Bridges.ERC20BridgeProxy, - deployer.addresses.Bridges.WethBridgeProxy - ], - [AccessMode.Public, AccessMode.Public, AccessMode.Public], - { nonce } - ); - await tx.wait(); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("initialize-l1-allow-list"); + + program + .option("--private-key ") + .option("--nonce ") + .action(async (cmd) => { + const wallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using wallet: ${wallet.address}`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await wallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const deployer = new Deployer({ deployWallet: wallet }); + + const allowListContract = deployer.l1AllowList(wallet); + const tx = await allowListContract.setBatchAccessMode( + [ + deployer.addresses.ZkSync.DiamondProxy, + deployer.addresses.Bridges.ERC20BridgeProxy, + deployer.addresses.Bridges.WethBridgeProxy, + ], + [AccessMode.Public, AccessMode.Public, AccessMode.Public], + { nonce } + ); + await tx.wait(); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/initialize-l2-weth-token.ts b/l1-contracts/scripts/initialize-l2-weth-token.ts index e43e84d14..bc1abfc16 100644 --- a/l1-contracts/scripts/initialize-l2-weth-token.ts +++ b/l1-contracts/scripts/initialize-l2-weth-token.ts @@ -1,191 +1,188 @@ -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import { Deployer } from '../src.ts/deploy'; -import { getNumberFromEnv, getTokens, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, web3Provider } from './utils'; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { Deployer } from "../src.ts/deploy"; +import { getNumberFromEnv, getTokens, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, web3Provider } from "./utils"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from "fs"; +import * as path from "path"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); -const contractArtifactsPath = path.join( - process.env.ZKSYNC_HOME as string, - 'era-contracts-lambda/l2-contracts/artifacts-zk/' -); -const l2BridgeArtifactsPath = path.join(contractArtifactsPath, 'cache-zk/solpp-generated-contracts/bridge/'); +const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "era-contracts-lambda/l2-contracts/artifacts-zk/"); +const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "cache-zk/solpp-generated-contracts/bridge/"); const openzeppelinTransparentProxyArtifactsPath = path.join( - contractArtifactsPath, - '@openzeppelin/contracts/proxy/transparent/' + contractArtifactsPath, + "@openzeppelin/contracts/proxy/transparent/" ); function readInterface(path: string, fileName: string, solFileName?: string) { - solFileName ??= fileName; - const abi = JSON.parse(fs.readFileSync(`${path}/${solFileName}.sol/${fileName}.json`, { encoding: 'utf-8' })).abi; - return new ethers.utils.Interface(abi); + solFileName ??= fileName; + const abi = JSON.parse(fs.readFileSync(`${path}/${solFileName}.sol/${fileName}.json`, { encoding: "utf-8" })).abi; + return new ethers.utils.Interface(abi); } -const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv('CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT'); -const L2_WETH_INTERFACE = readInterface(l2BridgeArtifactsPath, 'L2Weth'); +const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv("CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT"); +const L2_WETH_INTERFACE = readInterface(l2BridgeArtifactsPath, "L2Weth"); const TRANSPARENT_UPGRADEABLE_PROXY = readInterface( - openzeppelinTransparentProxyArtifactsPath, - 'TransparentUpgradeableProxy', - 'TransparentUpgradeableProxy' + openzeppelinTransparentProxyArtifactsPath, + "TransparentUpgradeableProxy", + "TransparentUpgradeableProxy" ); function getL2Calldata(l2WethBridgeAddress: string, l1WethTokenAddress: string, l2WethTokenImplAddress: string) { - const upgradeData = L2_WETH_INTERFACE.encodeFunctionData('initializeV2', [l2WethBridgeAddress, l1WethTokenAddress]); - return TRANSPARENT_UPGRADEABLE_PROXY.encodeFunctionData('upgradeToAndCall', [l2WethTokenImplAddress, upgradeData]); + const upgradeData = L2_WETH_INTERFACE.encodeFunctionData("initializeV2", [l2WethBridgeAddress, l1WethTokenAddress]); + return TRANSPARENT_UPGRADEABLE_PROXY.encodeFunctionData("upgradeToAndCall", [l2WethTokenImplAddress, upgradeData]); } async function getL1TxInfo( - deployer: Deployer, - to: string, - l2Calldata: string, - refundRecipient: string, - gasPrice: ethers.BigNumber + deployer: Deployer, + to: string, + l2Calldata: string, + refundRecipient: string, + gasPrice: ethers.BigNumber ) { - const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); - const l1Calldata = zksync.interface.encodeFunctionData('requestL2Transaction', [ - to, - 0, + const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); + const l1Calldata = zksync.interface.encodeFunctionData("requestL2Transaction", [ + to, + 0, + l2Calldata, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + [], // It is assumed that the target has already been deployed + refundRecipient, + ]); + + const neededValue = await zksync.l2TransactionBaseCost( + gasPrice, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + return { + to: zksync.address, + data: l1Calldata, + value: neededValue.toString(), + gasPrice: gasPrice.toString(), + }; +} + +async function main() { + const program = new Command(); + + program.version("0.1.0").name("initialize-l2-weth-token"); + + const l2WethBridgeAddress = process.env.CONTRACTS_L2_WETH_BRIDGE_ADDR; + const l2WethTokenProxyAddress = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; + const l2WethTokenImplAddress = process.env.CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR; + const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || "localhost"); + const l1WethTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; + + program + .command("prepare-calldata") + .option("--private-key ") + .option("--gas-price ") + .action(async (cmd) => { + if (!l1WethTokenAddress) { + console.log("Base Layer WETH address not provided. Skipping."); + return; + } + + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + const l2Calldata = getL2Calldata(l2WethBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); + const l1TxInfo = await getL1TxInfo( + deployer, + l2WethTokenProxyAddress, l2Calldata, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [], // It is assumed that the target has already been deployed - refundRecipient - ]); + ethers.constants.AddressZero, + gasPrice + ); + console.log(JSON.stringify(l1TxInfo, null, 4)); + console.log("IMPORTANT: gasPrice that you provide in the transaction should <= to the one provided above."); + }); - const neededValue = await zksync.l2TransactionBaseCost( + program + .command("instant-call") + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .action(async (cmd) => { + if (!l1WethTokenAddress) { + console.log("Base Layer WETH address not provided. Skipping."); + return; + } + + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using deployer nonce: ${nonce}`); + + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + const zkSync = deployer.zkSyncContract(deployWallet); + const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( gasPrice, DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - - return { - to: zksync.address, - data: l1Calldata, - value: neededValue.toString(), - gasPrice: gasPrice.toString() - }; -} + ); + const calldata = getL2Calldata(l2WethBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); -async function main() { - const program = new Command(); - - program.version('0.1.0').name('initialize-l2-weth-token'); - - const l2WethBridgeAddress = process.env.CONTRACTS_L2_WETH_BRIDGE_ADDR; - const l2WethTokenProxyAddress = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; - const l2WethTokenImplAddress = process.env.CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR; - const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || 'localhost'); - const l1WethTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == 'WETH')!.address; - - program - .command('prepare-calldata') - .option('--private-key ') - .option('--gas-price ') - .action(async (cmd) => { - if (!l1WethTokenAddress) { - console.log('Base Layer WETH address not provided. Skipping.'); - return; - } - - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - const l2Calldata = getL2Calldata(l2WethBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); - const l1TxInfo = await getL1TxInfo( - deployer, - l2WethTokenProxyAddress, - l2Calldata, - ethers.constants.AddressZero, - gasPrice - ); - console.log(JSON.stringify(l1TxInfo, null, 4)); - console.log('IMPORTANT: gasPrice that you provide in the transaction should <= to the one provided above.'); - }); - - program - .command('instant-call') - .option('--private-key ') - .option('--gas-price ') - .option('--nonce ') - .action(async (cmd) => { - if (!l1WethTokenAddress) { - console.log('Base Layer WETH address not provided. Skipping.'); - return; - } - - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using deployer nonce: ${nonce}`); - - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( - gasPrice, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - const calldata = getL2Calldata(l2WethBridgeAddress, l1WethTokenAddress, l2WethTokenImplAddress); - - const tx = await zkSync.requestL2Transaction( - l2WethTokenProxyAddress, - 0, - calldata, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [], - deployWallet.address, - { - gasPrice, - value: requiredValueToInitializeBridge - } - ); - - console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); - - const receipt = await tx.wait(); - - console.log(`L2 WETH token initialized, gasUsed: ${receipt.gasUsed.toString()}`); - }); - - await program.parseAsync(process.argv); + const tx = await zkSync.requestL2Transaction( + l2WethTokenProxyAddress, + 0, + calldata, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + [], + deployWallet.address, + { + gasPrice, + value: requiredValueToInitializeBridge, + } + ); + + console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); + + const receipt = await tx.wait(); + + console.log(`L2 WETH token initialized, gasUsed: ${receipt.gasUsed.toString()}`); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/initialize-validator.ts b/l1-contracts/scripts/initialize-validator.ts index 38a417661..cd3019b9d 100644 --- a/l1-contracts/scripts/initialize-validator.ts +++ b/l1-contracts/scripts/initialize-validator.ts @@ -1,58 +1,58 @@ -import { Command } from 'commander'; -import { Wallet } from 'ethers'; -import { Deployer } from '../src.ts/deploy'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import { web3Provider } from './utils'; +import { Command } from "commander"; +import { Wallet } from "ethers"; +import { Deployer } from "../src.ts/deploy"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { web3Provider } from "./utils"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from "fs"; +import * as path from "path"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function main() { - const program = new Command(); - - program - .option('--private-key ') - .option('--gas-price ') - .option('--nonce ') - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); - - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - const validatorTimelock = deployer.validatorTimelock(deployWallet); - const tx = await zkSync.setValidator(validatorTimelock.address, true); - console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}`); - const receipt = await tx.wait(); - - console.log(`Validator is set, gasUsed: ${receipt.gasUsed.toString()}`); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + const zkSync = deployer.zkSyncContract(deployWallet); + const validatorTimelock = deployer.validatorTimelock(deployWallet); + const tx = await zkSync.setValidator(validatorTimelock.address, true); + console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}`); + const receipt = await tx.wait(); + + console.log(`Validator is set, gasUsed: ${receipt.gasUsed.toString()}`); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/initialize-weth-bridges.ts b/l1-contracts/scripts/initialize-weth-bridges.ts index f366ffeb4..226bdd564 100644 --- a/l1-contracts/scripts/initialize-weth-bridges.ts +++ b/l1-contracts/scripts/initialize-weth-bridges.ts @@ -1,111 +1,106 @@ -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import { Deployer } from '../src.ts/deploy'; -import { applyL1ToL2Alias, getNumberFromEnv, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, web3Provider } from './utils'; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { Deployer } from "../src.ts/deploy"; +import { applyL1ToL2Alias, getNumberFromEnv, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, web3Provider } from "./utils"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from "fs"; +import * as path from "path"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); -const contractArtifactsPath = path.join( - process.env.ZKSYNC_HOME as string, - 'era-contracts-lambda/l2-contracts/artifacts-zk/' -); -const l2BridgeArtifactsPath = path.join(contractArtifactsPath, 'cache-zk/solpp-generated-contracts/bridge/'); +const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "era-contracts-lambda/l2-contracts/artifacts-zk/"); +const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "cache-zk/solpp-generated-contracts/bridge/"); const openzeppelinTransparentProxyArtifactsPath = path.join( - contractArtifactsPath, - '@openzeppelin/contracts/proxy/transparent/' + contractArtifactsPath, + "@openzeppelin/contracts/proxy/transparent/" ); function readBytecode(path: string, fileName: string) { - return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: 'utf-8' })).bytecode; + return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).bytecode; } const L2_WETH_BRIDGE_PROXY_BYTECODE = readBytecode( - openzeppelinTransparentProxyArtifactsPath, - 'TransparentUpgradeableProxy' + openzeppelinTransparentProxyArtifactsPath, + "TransparentUpgradeableProxy" ); -const L2_WETH_BRIDGE_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, 'L2WethBridge'); -const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv('CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT'); +const L2_WETH_BRIDGE_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2WethBridge"); +const DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = getNumberFromEnv("CONTRACTS_DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT"); async function main() { - const program = new Command(); - - program.version('0.1.0').name('initialize-weth-bridges'); - - program - .option('--private-key ') - .option('--gas-price ') - .option('--nonce ') - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/0" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using deployer nonce: ${nonce}`); - - const l2WethAddress = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; - - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - const l1WethBridge = deployer.defaultWethBridge(deployWallet); - - const l1GovernorAddress = await zkSync.getGovernor(); - // Check whether governor is a smart contract on L1 to apply alias if needed. - const l1GovernorCodeSize = ethers.utils.hexDataLength( - await deployWallet.provider.getCode(l1GovernorAddress) - ); - const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); - - // There will be two deployments done during the initial initialization - const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( - gasPrice, - DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - - const tx = await l1WethBridge.initialize( - [L2_WETH_BRIDGE_IMPLEMENTATION_BYTECODE, L2_WETH_BRIDGE_PROXY_BYTECODE], - l2WethAddress, - l2GovernorAddress, - requiredValueToInitializeBridge, - requiredValueToInitializeBridge, - { - gasPrice, - value: requiredValueToInitializeBridge.mul(2) - } - ); - console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); - - const receipt = await tx.wait(); - - console.log(`WETH bridge initialized, gasUsed: ${receipt.gasUsed.toString()}`); - console.log(`CONTRACTS_L2_WETH_BRIDGE_ADDR=${await l1WethBridge.l2Bridge()}`); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("initialize-weth-bridges"); + + program + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/0" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using deployer nonce: ${nonce}`); + + const l2WethAddress = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; + + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + const zkSync = deployer.zkSyncContract(deployWallet); + const l1WethBridge = deployer.defaultWethBridge(deployWallet); + + const l1GovernorAddress = await zkSync.getGovernor(); + // Check whether governor is a smart contract on L1 to apply alias if needed. + const l1GovernorCodeSize = ethers.utils.hexDataLength(await deployWallet.provider.getCode(l1GovernorAddress)); + const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); + + // There will be two deployments done during the initial initialization + const requiredValueToInitializeBridge = await zkSync.l2TransactionBaseCost( + gasPrice, + DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + + const tx = await l1WethBridge.initialize( + [L2_WETH_BRIDGE_IMPLEMENTATION_BYTECODE, L2_WETH_BRIDGE_PROXY_BYTECODE], + l2WethAddress, + l2GovernorAddress, + requiredValueToInitializeBridge, + requiredValueToInitializeBridge, + { + gasPrice, + value: requiredValueToInitializeBridge.mul(2), + } + ); + console.log(`Transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...`); + + const receipt = await tx.wait(); + + console.log(`WETH bridge initialized, gasUsed: ${receipt.gasUsed.toString()}`); + console.log(`CONTRACTS_L2_WETH_BRIDGE_ADDR=${await l1WethBridge.l2Bridge()}`); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/migrate-governance.ts b/l1-contracts/scripts/migrate-governance.ts index e158e8d5b..347d39977 100644 --- a/l1-contracts/scripts/migrate-governance.ts +++ b/l1-contracts/scripts/migrate-governance.ts @@ -1,249 +1,245 @@ /// Temporary script that generated the needed calldata for the migration of the governance. -import { Command } from 'commander'; -import { BigNumber, ethers, Wallet } from 'ethers'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import * as fs from 'fs'; -import * as hre from 'hardhat'; -import { Deployer } from '../src.ts/deploy'; -import { applyL1ToL2Alias, getAddressFromEnv, getNumberFromEnv, web3Provider } from './utils'; +import { Command } from "commander"; +import { BigNumber, ethers, Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import * as fs from "fs"; +import * as hre from "hardhat"; +import { Deployer } from "../src.ts/deploy"; +import { applyL1ToL2Alias, getAddressFromEnv, getNumberFromEnv, web3Provider } from "./utils"; -import { getL1TxInfo } from '../../l2-contracts/src/utils'; +import { getL1TxInfo } from "../../l2-contracts/src/utils"; -import { Provider } from 'zksync-web3'; -import { UpgradeableBeaconFactory } from '../../l2-contracts/typechain/UpgradeableBeaconFactory'; +import { Provider } from "zksync-web3"; +import { UpgradeableBeaconFactory } from "../../l2-contracts/typechain/UpgradeableBeaconFactory"; const provider = web3Provider(); -const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT')); +const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT")); const L2ERC20BridgeABI = JSON.parse( - fs - .readFileSync( - '../l2-contracts/artifacts-zk/cache-zk/solpp-generated-contracts/bridge/L2ERC20Bridge.sol/L2ERC20Bridge.json' - ) - .toString() + fs + .readFileSync( + "../l2-contracts/artifacts-zk/cache-zk/solpp-generated-contracts/bridge/L2ERC20Bridge.sol/L2ERC20Bridge.json" + ) + .toString() ).abi; interface TxInfo { - data: string; - to: string; - value?: string; + data: string; + to: string; + value?: string; } async function getERC20BeaconAddress(l2Erc20BridgeAddress: string) { - const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const contract = new ethers.Contract(l2Erc20BridgeAddress, L2ERC20BridgeABI, provider); - return await contract.l2TokenBeacon(); + const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const contract = new ethers.Contract(l2Erc20BridgeAddress, L2ERC20BridgeABI, provider); + return await contract.l2TokenBeacon(); } function displayTx(msg: string, info: TxInfo) { - console.log(msg); - console.log(JSON.stringify(info, null, 2), '\n'); + console.log(msg); + console.log(JSON.stringify(info, null, 2), "\n"); } async function main() { - const program = new Command(); - - program.version('0.1.0').name('migrate-governance'); - - program - .option('--new-governance-address ') - .option('--gas-price ') - .option('--refund-recipient ') - .action(async (cmd) => { - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const refundRecipient = cmd.refundRecipient; - console.log(`Using refund recipient: ${refundRecipient}`); - - // This action is very dangerous, and so we double check that the governance in env is the same - // one as the user provided manually. - const governanceAddressFromEnv = getAddressFromEnv('CONTRACTS_GOVERNANCE_ADDR').toLowerCase(); - const userProvidedAddress = cmd.newGovernanceAddress.toLowerCase(); - if (governanceAddressFromEnv !== userProvidedAddress) { - throw new Error('Governance mismatch'); - } - - // We won't be making any transactions with this wallet, we just need - // it to initialize the Deployer object. - const deployWallet = Wallet.createRandom(); - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - const expectedDeployedBytecode = hre.artifacts.readArtifactSync('Governance').deployedBytecode; - - const isBytecodeCorrect = - (await provider.getCode(userProvidedAddress)).toLowerCase() === expectedDeployedBytecode.toLowerCase(); - if (!isBytecodeCorrect) { - throw new Error('The address does not contain governance bytecode'); - } - - console.log('Firstly, the current governor should transfer its ownership to the new governance contract.'); - console.log('All the transactions below can be executed in one batch'); - - // Step 1. Transfer ownership of all the contracts to the new governor. - - // Below we are preparing the calldata for the L1 transactions - const zkSync = deployer.zkSyncContract(deployWallet); - const allowlist = deployer.l1AllowList(deployWallet); - const validatorTimelock = deployer.validatorTimelock(deployWallet); - - const l1Erc20Bridge = deployer.transparentUpgradableProxyContract( - deployer.addresses.Bridges.ERC20BridgeProxy, - deployWallet - ); - - const erc20MigrationTx = l1Erc20Bridge.interface.encodeFunctionData('changeAdmin', [ - governanceAddressFromEnv - ]); - displayTx('L1 ERC20 bridge migration calldata:', { - data: erc20MigrationTx, - to: l1Erc20Bridge.address - }); - - const zkSyncSetPendingGovernor = zkSync.interface.encodeFunctionData('setPendingGovernor', [ - governanceAddressFromEnv - ]); - displayTx('zkSync Diamond Proxy migration calldata:', { - data: zkSyncSetPendingGovernor, - to: zkSync.address - }); - - const allowListGovernorMigration = allowlist.interface.encodeFunctionData('transferOwnership', [ - governanceAddressFromEnv - ]); - displayTx('AllowList migration calldata:', { - data: allowListGovernorMigration, - to: allowlist.address - }); - - const validatorTimelockMigration = validatorTimelock.interface.encodeFunctionData('transferOwnership', [ - governanceAddressFromEnv - ]); - displayTx('Validator timelock migration calldata:', { - data: validatorTimelockMigration, - to: validatorTimelock.address - }); - - // Below, we prepare the transactions to migrate the L2 contracts. - - // Note that since these are L2 contracts, the governance must be aliased. - const aliasedNewGovernor = applyL1ToL2Alias(governanceAddressFromEnv); - - // L2 ERC20 bridge as well as Weth token are a transparent upgradable proxy. - const l2ERC20Bridge = deployer.transparentUpgradableProxyContract( - process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR!, - deployWallet - ); - const l2Erc20BridgeCalldata = l2ERC20Bridge.interface.encodeFunctionData('changeAdmin', [ - aliasedNewGovernor - ]); - const l2TxForErc20Bridge = await getL1TxInfo( - deployer, - l2ERC20Bridge.address, - l2Erc20BridgeCalldata, - refundRecipient, - gasPrice, - priorityTxMaxGasLimit, - provider - ); - displayTx('L2 ERC20 bridge changeAdmin: ', l2TxForErc20Bridge); - - const l2wethToken = deployer.transparentUpgradableProxyContract( - process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR!, - deployWallet - ); - const l2WethUpgradeCalldata = l2wethToken.interface.encodeFunctionData('changeAdmin', [aliasedNewGovernor]); - const l2TxForWethUpgrade = await getL1TxInfo( - deployer, - l2wethToken.address, - l2WethUpgradeCalldata, - refundRecipient, - gasPrice, - priorityTxMaxGasLimit, - provider - ); - displayTx('L2 Weth upgrade: ', l2TxForWethUpgrade); - - // L2 Tokens are BeaconProxies - const l2Erc20BeaconAddress: string = await getERC20BeaconAddress(l2ERC20Bridge.address); - const l2Erc20TokenBeacon = UpgradeableBeaconFactory.connect(l2Erc20BeaconAddress, deployWallet); - const l2Erc20BeaconCalldata = l2Erc20TokenBeacon.interface.encodeFunctionData('transferOwnership', [ - aliasedNewGovernor - ]); - const l2TxForErc20BeaconUpgrade = await getL1TxInfo( - deployer, - l2Erc20BeaconAddress, - l2Erc20BeaconCalldata, - refundRecipient, - gasPrice, - priorityTxMaxGasLimit, - provider - ); - displayTx('L2 ERC20 beacon upgrade: ', l2TxForErc20BeaconUpgrade); - - // Small delimeter for better readability. - console.log('\n\n\n', '-'.repeat(20), '\n\n\n'); - - console.log('Secondly, the new governor needs to accept all the roles where they need to be accepted.'); - - // Step 2. Accept the roles on L1. Transparent proxy and Beacon proxy contracts do NOT require accepting new ownership. - // However, the following do require: - // - zkSync Diamond Proxy - // - ValidatorTimelock. - // - Allowlist. - - const calls = [ - { - target: zkSync.address, - value: 0, - data: zkSync.interface.encodeFunctionData('acceptGovernor') - }, - { - target: allowlist.address, - value: 0, - data: allowlist.interface.encodeFunctionData('acceptOwnership') - }, - { - target: validatorTimelock.address, - value: 0, - data: validatorTimelock.interface.encodeFunctionData('acceptOwnership') - } - ]; - - const operation = { - calls: calls, - predecessor: ethers.constants.HashZero, - salt: ethers.constants.HashZero - }; - - const governance = deployer.governanceContract(deployWallet); - - const scheduleTransparentCalldata = governance.interface.encodeFunctionData('scheduleTransparent', [ - operation, - 0 - ]); - displayTx('Schedule transparent calldata:\n', { - data: scheduleTransparentCalldata, - to: governance.address - }); - - const executeCalldata = governance.interface.encodeFunctionData('execute', [operation]); - displayTx('Execute calldata:\n', { - data: executeCalldata, - to: governance.address - }); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("migrate-governance"); + + program + .option("--new-governance-address ") + .option("--gas-price ") + .option("--refund-recipient ") + .action(async (cmd) => { + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const refundRecipient = cmd.refundRecipient; + console.log(`Using refund recipient: ${refundRecipient}`); + + // This action is very dangerous, and so we double check that the governance in env is the same + // one as the user provided manually. + const governanceAddressFromEnv = getAddressFromEnv("CONTRACTS_GOVERNANCE_ADDR").toLowerCase(); + const userProvidedAddress = cmd.newGovernanceAddress.toLowerCase(); + if (governanceAddressFromEnv !== userProvidedAddress) { + throw new Error("Governance mismatch"); + } + + // We won't be making any transactions with this wallet, we just need + // it to initialize the Deployer object. + const deployWallet = Wallet.createRandom(); + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + const expectedDeployedBytecode = hre.artifacts.readArtifactSync("Governance").deployedBytecode; + + const isBytecodeCorrect = + (await provider.getCode(userProvidedAddress)).toLowerCase() === expectedDeployedBytecode.toLowerCase(); + if (!isBytecodeCorrect) { + throw new Error("The address does not contain governance bytecode"); + } + + console.log("Firstly, the current governor should transfer its ownership to the new governance contract."); + console.log("All the transactions below can be executed in one batch"); + + // Step 1. Transfer ownership of all the contracts to the new governor. + + // Below we are preparing the calldata for the L1 transactions + const zkSync = deployer.zkSyncContract(deployWallet); + const allowlist = deployer.l1AllowList(deployWallet); + const validatorTimelock = deployer.validatorTimelock(deployWallet); + + const l1Erc20Bridge = deployer.transparentUpgradableProxyContract( + deployer.addresses.Bridges.ERC20BridgeProxy, + deployWallet + ); + + const erc20MigrationTx = l1Erc20Bridge.interface.encodeFunctionData("changeAdmin", [governanceAddressFromEnv]); + displayTx("L1 ERC20 bridge migration calldata:", { + data: erc20MigrationTx, + to: l1Erc20Bridge.address, + }); + + const zkSyncSetPendingGovernor = zkSync.interface.encodeFunctionData("setPendingGovernor", [ + governanceAddressFromEnv, + ]); + displayTx("zkSync Diamond Proxy migration calldata:", { + data: zkSyncSetPendingGovernor, + to: zkSync.address, + }); + + const allowListGovernorMigration = allowlist.interface.encodeFunctionData("transferOwnership", [ + governanceAddressFromEnv, + ]); + displayTx("AllowList migration calldata:", { + data: allowListGovernorMigration, + to: allowlist.address, + }); + + const validatorTimelockMigration = validatorTimelock.interface.encodeFunctionData("transferOwnership", [ + governanceAddressFromEnv, + ]); + displayTx("Validator timelock migration calldata:", { + data: validatorTimelockMigration, + to: validatorTimelock.address, + }); + + // Below, we prepare the transactions to migrate the L2 contracts. + + // Note that since these are L2 contracts, the governance must be aliased. + const aliasedNewGovernor = applyL1ToL2Alias(governanceAddressFromEnv); + + // L2 ERC20 bridge as well as Weth token are a transparent upgradable proxy. + const l2ERC20Bridge = deployer.transparentUpgradableProxyContract( + process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR!, + deployWallet + ); + const l2Erc20BridgeCalldata = l2ERC20Bridge.interface.encodeFunctionData("changeAdmin", [aliasedNewGovernor]); + const l2TxForErc20Bridge = await getL1TxInfo( + deployer, + l2ERC20Bridge.address, + l2Erc20BridgeCalldata, + refundRecipient, + gasPrice, + priorityTxMaxGasLimit, + provider + ); + displayTx("L2 ERC20 bridge changeAdmin: ", l2TxForErc20Bridge); + + const l2wethToken = deployer.transparentUpgradableProxyContract( + process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR!, + deployWallet + ); + const l2WethUpgradeCalldata = l2wethToken.interface.encodeFunctionData("changeAdmin", [aliasedNewGovernor]); + const l2TxForWethUpgrade = await getL1TxInfo( + deployer, + l2wethToken.address, + l2WethUpgradeCalldata, + refundRecipient, + gasPrice, + priorityTxMaxGasLimit, + provider + ); + displayTx("L2 Weth upgrade: ", l2TxForWethUpgrade); + + // L2 Tokens are BeaconProxies + const l2Erc20BeaconAddress: string = await getERC20BeaconAddress(l2ERC20Bridge.address); + const l2Erc20TokenBeacon = UpgradeableBeaconFactory.connect(l2Erc20BeaconAddress, deployWallet); + const l2Erc20BeaconCalldata = l2Erc20TokenBeacon.interface.encodeFunctionData("transferOwnership", [ + aliasedNewGovernor, + ]); + const l2TxForErc20BeaconUpgrade = await getL1TxInfo( + deployer, + l2Erc20BeaconAddress, + l2Erc20BeaconCalldata, + refundRecipient, + gasPrice, + priorityTxMaxGasLimit, + provider + ); + displayTx("L2 ERC20 beacon upgrade: ", l2TxForErc20BeaconUpgrade); + + // Small delimeter for better readability. + console.log("\n\n\n", "-".repeat(20), "\n\n\n"); + + console.log("Secondly, the new governor needs to accept all the roles where they need to be accepted."); + + // Step 2. Accept the roles on L1. Transparent proxy and Beacon proxy contracts do NOT require accepting new ownership. + // However, the following do require: + // - zkSync Diamond Proxy + // - ValidatorTimelock. + // - Allowlist. + + const calls = [ + { + target: zkSync.address, + value: 0, + data: zkSync.interface.encodeFunctionData("acceptGovernor"), + }, + { + target: allowlist.address, + value: 0, + data: allowlist.interface.encodeFunctionData("acceptOwnership"), + }, + { + target: validatorTimelock.address, + value: 0, + data: validatorTimelock.interface.encodeFunctionData("acceptOwnership"), + }, + ]; + + const operation = { + calls: calls, + predecessor: ethers.constants.HashZero, + salt: ethers.constants.HashZero, + }; + + const governance = deployer.governanceContract(deployWallet); + + const scheduleTransparentCalldata = governance.interface.encodeFunctionData("scheduleTransparent", [ + operation, + 0, + ]); + displayTx("Schedule transparent calldata:\n", { + data: scheduleTransparentCalldata, + to: governance.address, + }); + + const executeCalldata = governance.interface.encodeFunctionData("execute", [operation]); + displayTx("Execute calldata:\n", { + data: executeCalldata, + to: governance.address, + }); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/read-variable.ts b/l1-contracts/scripts/read-variable.ts index b116e4e18..880320fb4 100644 --- a/l1-contracts/scripts/read-variable.ts +++ b/l1-contracts/scripts/read-variable.ts @@ -1,79 +1,79 @@ -import { Command } from 'commander'; -import { BigNumber, ethers } from 'ethers'; -import * as hre from 'hardhat'; -import { web3Provider } from './utils'; +import { Command } from "commander"; +import { BigNumber, ethers } from "ethers"; +import * as hre from "hardhat"; +import { web3Provider } from "./utils"; const provider = web3Provider(); const cache: Map = new Map(); async function getStorageAt(address: string, slot: BigNumber): Promise { - if (!cache.has(slot)) { - cache.set(slot, await provider.getStorageAt(address, slot)); - } - return cache.get(slot); + if (!cache.has(slot)) { + cache.set(slot, await provider.getStorageAt(address, slot)); + } + return cache.get(slot); } // Read bytes from storage like hex string async function readBytes(slot: BigNumber, shift: number, bytes: number, address: string): Promise { - const data = await getStorageAt(address, slot); - return '0x' + data.substr(66 - bytes * 2 - shift * 2, bytes * 2); + const data = await getStorageAt(address, slot); + return "0x" + data.substr(66 - bytes * 2 - shift * 2, bytes * 2); } // Read dynamic sized bytes (encoding: bytes) async function readDynamicBytes(slot: BigNumber, address: string): Promise { - const data = await getStorageAt(address, slot); - if (Number.parseInt(data.substr(64, 2), 16) % 2 === 0) { - const length = Number.parseInt(data.substr(64, 2), 16) / 2; - return '0x' + data.substr(2, 2 * length); - } else { - const length = (Number.parseInt(data, 16) - 1) / 2; - const firstSlot = BigNumber.from(ethers.utils.solidityKeccak256(['uint'], [slot])); - const slots = []; - for (let slotShift = 0; slotShift * 32 < length; slotShift++) { - slots.push(getStorageAt(address, firstSlot.add(slotShift))); - } + const data = await getStorageAt(address, slot); + if (Number.parseInt(data.substr(64, 2), 16) % 2 === 0) { + const length = Number.parseInt(data.substr(64, 2), 16) / 2; + return "0x" + data.substr(2, 2 * length); + } else { + const length = (Number.parseInt(data, 16) - 1) / 2; + const firstSlot = BigNumber.from(ethers.utils.solidityKeccak256(["uint"], [slot])); + const slots = []; + for (let slotShift = 0; slotShift * 32 < length; slotShift++) { + slots.push(getStorageAt(address, firstSlot.add(slotShift))); + } - const lastLength = length % 32; - let hex: string = '0x'; - for (let i = 0; i < slots.length; i++) { - if (i === slots.length - 1) { - hex += (await slots[i]).substr(2, lastLength * 2); - } else { - hex += (await slots[i]).substr(2, 64); - } - } - return hex; + const lastLength = length % 32; + let hex: string = "0x"; + for (let i = 0; i < slots.length; i++) { + if (i === slots.length - 1) { + hex += (await slots[i]).substr(2, lastLength * 2); + } else { + hex += (await slots[i]).substr(2, 64); + } } + return hex; + } } // Functions for read all types, except user defined structs and arrays async function readString(slot: BigNumber, address: string): Promise { - return ethers.utils.toUtf8String(await readDynamicBytes(slot, address)); + return ethers.utils.toUtf8String(await readDynamicBytes(slot, address)); } async function readNumber(slot: BigNumber, shift: number, label: string, address: string): Promise { - let bytes: number; - if (label.substr(0, 3) === 'int') { - bytes = +label.substring(3, label.length) / 8; - } else { - bytes = +label.substring(4, label.length) / 8; - } - let data: string = await readBytes(slot, shift, bytes, address); - data = ethers.utils.hexZeroPad(data, 32); - return ethers.utils.defaultAbiCoder.decode([label], data).toString(); + let bytes: number; + if (label.substr(0, 3) === "int") { + bytes = +label.substring(3, label.length) / 8; + } else { + bytes = +label.substring(4, label.length) / 8; + } + let data: string = await readBytes(slot, shift, bytes, address); + data = ethers.utils.hexZeroPad(data, 32); + return ethers.utils.defaultAbiCoder.decode([label], data).toString(); } async function readBoolean(slot: BigNumber, shift: number, address: string): Promise { - return (await readNumber(slot, shift, 'uint8', address)) !== '0'; + return (await readNumber(slot, shift, "uint8", address)) !== "0"; } async function readAddress(slot: BigNumber, shift: number, address: string): Promise { - return readBytes(slot, shift, 20, address); + return readBytes(slot, shift, 20, address); } async function readEnum(slot: BigNumber, shift: number, bytes: number, address: string): Promise { - return await readNumber(slot, shift, 'uint' + bytes * 8, address); + return await readNumber(slot, shift, "uint" + bytes * 8, address); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -81,299 +81,299 @@ let types: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any async function readPrimitive(slot: BigNumber, shift: number, address: string, type: string): Promise { - if (type.substr(0, 5) === 't_int' || type.substr(0, 6) === 't_uint') { - return readNumber(slot, shift, types[type].label, address); - } - if (type === 't_bool') { - return readBoolean(slot, shift, address); - } - if (type === 't_address' || type === 't_address_payable') { - return readAddress(slot, shift, address); - } - if (type === 't_bytes_storage') { - return readDynamicBytes(slot, address); - } - if (type.substr(0, 7) === 't_bytes') { - return readBytes(slot, shift, types[type].numberOfBytes, address); - } - if (type === 't_string_storage') { - return readString(slot, address); - } - if (type.substr(0, 6) === 't_enum') { - return readEnum(slot, shift, types[type].numberOfBytes, address); - } + if (type.substr(0, 5) === "t_int" || type.substr(0, 6) === "t_uint") { + return readNumber(slot, shift, types[type].label, address); + } + if (type === "t_bool") { + return readBoolean(slot, shift, address); + } + if (type === "t_address" || type === "t_address_payable") { + return readAddress(slot, shift, address); + } + if (type === "t_bytes_storage") { + return readDynamicBytes(slot, address); + } + if (type.substr(0, 7) === "t_bytes") { + return readBytes(slot, shift, types[type].numberOfBytes, address); + } + if (type === "t_string_storage") { + return readString(slot, address); + } + if (type.substr(0, 6) === "t_enum") { + return readEnum(slot, shift, types[type].numberOfBytes, address); + } } // Read user defined struct async function readStruct(slot: BigNumber, address: string, type: string): Promise { - const result = {}; - const data = new Map(); - types[type].members.forEach((member) => { - data.set( - member.label, - readVariable(slot.add(Number.parseInt(member.slot, 10)), member.offset, address, member.type) - ); - }); - for (const [key, value] of data) { - result[key] = await value; - } - return result; + const result = {}; + const data = new Map(); + types[type].members.forEach((member) => { + data.set( + member.label, + readVariable(slot.add(Number.parseInt(member.slot, 10)), member.offset, address, member.type) + ); + }); + for (const [key, value] of data) { + result[key] = await value; + } + return result; } // Read array (Static or dynamic sized) // eslint-disable-next-line @typescript-eslint/no-explicit-any async function readArray(slot: BigNumber, address: string, type: string): Promise { - let length: number; - const baseType = types[type].base; - if (types[type].encoding === 'dynamic_array') { - length = +(await readNumber(slot, 0, 'uint256', address)); - slot = BigNumber.from(ethers.utils.solidityKeccak256(['uint'], [slot])); - } else { - length = Number.parseInt(type.substring(type.lastIndexOf(')') + 1, type.lastIndexOf('_')), 10); + let length: number; + const baseType = types[type].base; + if (types[type].encoding === "dynamic_array") { + length = +(await readNumber(slot, 0, "uint256", address)); + slot = BigNumber.from(ethers.utils.solidityKeccak256(["uint"], [slot])); + } else { + length = Number.parseInt(type.substring(type.lastIndexOf(")") + 1, type.lastIndexOf("_")), 10); + } + const baseBytes = +types[baseType].numberOfBytes; + const data = []; + if (baseBytes < 32) { + let shift: number = -baseBytes; + for (let i = 0; i < length; i++) { + shift += baseBytes; + if (shift + baseBytes > 32) { + shift = 0; + slot = slot.add(1); + } + data.push(readVariable(slot, shift, address, baseType)); } - const baseBytes = +types[baseType].numberOfBytes; - const data = []; - if (baseBytes < 32) { - let shift: number = -baseBytes; - for (let i = 0; i < length; i++) { - shift += baseBytes; - if (shift + baseBytes > 32) { - shift = 0; - slot = slot.add(1); - } - data.push(readVariable(slot, shift, address, baseType)); - } - } else { - for (let i = 0; i < length; i++) { - data.push(readVariable(slot, 0, address, baseType)); - slot = slot.add(baseBytes / 32); - } + } else { + for (let i = 0; i < length; i++) { + data.push(readVariable(slot, 0, address, baseType)); + slot = slot.add(baseBytes / 32); } - return Promise.all(data); + } + return Promise.all(data); } // Read any type, except mapping (it needs key for reading) // eslint-disable-next-line @typescript-eslint/no-explicit-any async function readVariable(slot: BigNumber, shift: number, address: string, type: string): Promise { - if (type.substr(0, 7) === 't_array') return readArray(slot, address, type); - if (type.substr(0, 8) === 't_struct') return readStruct(slot, address, type); - return readPrimitive(slot, shift, address, type); + if (type.substr(0, 7) === "t_array") return readArray(slot, address, type); + if (type.substr(0, 8) === "t_struct") return readStruct(slot, address, type); + return readPrimitive(slot, shift, address, type); } // Read field of struct // eslint-disable-next-line @typescript-eslint/no-explicit-any async function readPartOfStruct(slot: BigNumber, address: string, type: string, params: string[]): Promise { - const last = params.pop(); - const member = types[type].members.find((element) => { - return element.label === last; - }); - if (!member) throw new Error('Invalid field name of struct'); - return readPartOfVariable(slot.add(Number.parseInt(member.slot, 10)), member.offset, address, member.type, params); + const last = params.pop(); + const member = types[type].members.find((element) => { + return element.label === last; + }); + if (!member) throw new Error("Invalid field name of struct"); + return readPartOfVariable(slot.add(Number.parseInt(member.slot, 10)), member.offset, address, member.type, params); } // Read array element by index // eslint-disable-next-line @typescript-eslint/no-explicit-any async function readPartOfArray(slot: BigNumber, address: string, type: string, params: string[]): Promise { - const index = +params.pop(); - const baseType = types[type].base; - if (types[type].encoding === 'dynamic_array') { - slot = BigNumber.from(ethers.utils.solidityKeccak256(['uint'], [slot])); - } - const baseBytes = +types[baseType].numberOfBytes; - if (baseBytes < 32) { - const inOne = Math.floor(32 / baseBytes); - slot = slot.add(Math.floor(index / inOne)); - const shift = baseBytes * (index % inOne); - return readPartOfVariable(slot, shift, address, baseType, params); - } else { - slot = slot.add((index * baseBytes) / 32); - return readPartOfVariable(slot, 0, address, baseType, params); - } + const index = +params.pop(); + const baseType = types[type].base; + if (types[type].encoding === "dynamic_array") { + slot = BigNumber.from(ethers.utils.solidityKeccak256(["uint"], [slot])); + } + const baseBytes = +types[baseType].numberOfBytes; + if (baseBytes < 32) { + const inOne = Math.floor(32 / baseBytes); + slot = slot.add(Math.floor(index / inOne)); + const shift = baseBytes * (index % inOne); + return readPartOfVariable(slot, shift, address, baseType, params); + } else { + slot = slot.add((index * baseBytes) / 32); + return readPartOfVariable(slot, 0, address, baseType, params); + } } // Encode key for mapping type function encodeKey(key: string, type: string): string { - if (type === 't_bool') { - if (key === 'false' || key === '0') { - return ethers.utils.defaultAbiCoder.encode(['bool'], [false]); - } else { - return ethers.utils.defaultAbiCoder.encode(['bool'], [true]); - } - } - if (type === 't_address' || type === 't_address_payable') { - if (key.length === 42) { - return '0x' + key.substring(2, key.length).padStart(64, '0'); - } else { - return '0x' + key.padStart(64, '0'); - } + if (type === "t_bool") { + if (key === "false" || key === "0") { + return ethers.utils.defaultAbiCoder.encode(["bool"], [false]); + } else { + return ethers.utils.defaultAbiCoder.encode(["bool"], [true]); } - if (type === 't_string_memory_ptr') { - return ethers.utils.hexlify(ethers.utils.toUtf8Bytes(key)); + } + if (type === "t_address" || type === "t_address_payable") { + if (key.length === 42) { + return "0x" + key.substring(2, key.length).padStart(64, "0"); + } else { + return "0x" + key.padStart(64, "0"); } - if (type === 't_bytes_memory_ptr') { - if (key.substr(0, 2) === '0x') { - return key; - } else { - return '0x' + key; - } + } + if (type === "t_string_memory_ptr") { + return ethers.utils.hexlify(ethers.utils.toUtf8Bytes(key)); + } + if (type === "t_bytes_memory_ptr") { + if (key.substr(0, 2) === "0x") { + return key; + } else { + return "0x" + key; } - return ethers.utils.defaultAbiCoder.encode([types[type].label], [+key]); + } + return ethers.utils.defaultAbiCoder.encode([types[type].label], [+key]); } // Read mapping element by key // eslint-disable-next-line @typescript-eslint/no-explicit-any async function readPartOfMap(slot: BigNumber, address: string, type: string, params: string[]): Promise { - const key = params.pop(); - const valueType = types[type].value; - const keyType = types[type].key; - const encodedKey = encodeKey(key, keyType); - const encodedSlot = ethers.utils.defaultAbiCoder.encode(['uint'], [slot]); - const hex = encodedKey + encodedSlot.substring(2, encodedSlot.length); - slot = BigNumber.from(ethers.utils.keccak256(ethers.utils.arrayify(hex))); - return readPartOfVariable(slot, 0, address, valueType, params); + const key = params.pop(); + const valueType = types[type].value; + const keyType = types[type].key; + const encodedKey = encodeKey(key, keyType); + const encodedSlot = ethers.utils.defaultAbiCoder.encode(["uint"], [slot]); + const hex = encodedKey + encodedSlot.substring(2, encodedSlot.length); + slot = BigNumber.from(ethers.utils.keccak256(ethers.utils.arrayify(hex))); + return readPartOfVariable(slot, 0, address, valueType, params); } // Read part of variable (By indexes, field names, keys) async function readPartOfVariable( - slot: BigNumber, - shift: number, - address: string, - type: string, - params: string[] - // eslint-disable-next-line @typescript-eslint/no-explicit-any + slot: BigNumber, + shift: number, + address: string, + type: string, + params: string[] + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { - if (params.length === 0) { - return readVariable(slot, shift, address, type); - } - if (type.substr(0, 7) === 't_array') { - return readPartOfArray(slot, address, type, params); - } - if (type.substr(0, 8) === 't_struct') { - return readPartOfStruct(slot, address, type, params); - } - if (types[type].encoding === 'mapping') { - return readPartOfMap(slot, address, type, params); - } - return readPrimitive(slot, shift, address, type); + if (params.length === 0) { + return readVariable(slot, shift, address, type); + } + if (type.substr(0, 7) === "t_array") { + return readPartOfArray(slot, address, type, params); + } + if (type.substr(0, 8) === "t_struct") { + return readPartOfStruct(slot, address, type, params); + } + if (types[type].encoding === "mapping") { + return readPartOfMap(slot, address, type, params); + } + return readPrimitive(slot, shift, address, type); } // Get reverse array of indexes, struct fields and mapping keys from name // Field names, keys cannot contain square brackets or points function parseName(fullName: string): string[] { - const firstPoint = fullName.indexOf('.'); - const firstBracket = fullName.indexOf('['); - if (firstPoint === -1 && firstBracket === -1) { - return []; - } - const result = []; - let first: number; - if (firstPoint === -1) { - first = firstBracket; - } else if (firstBracket === -1) { - first = firstPoint; + const firstPoint = fullName.indexOf("."); + const firstBracket = fullName.indexOf("["); + if (firstPoint === -1 && firstBracket === -1) { + return []; + } + const result = []; + let first: number; + if (firstPoint === -1) { + first = firstBracket; + } else if (firstBracket === -1) { + first = firstPoint; + } else { + first = Math.min(firstPoint, firstBracket); + } + let str: string = ""; + let bracket: boolean = false; + for (let i = first; i < fullName.length; i++) { + if (fullName.charAt(i) === ".") { + if (bracket) { + throw new Error("Field names, keys cannot contain square brackets or points"); + } + if (i !== first) result.push(str); + str = ""; + } else if (fullName.charAt(i) === "[") { + if (bracket) { + throw new Error("Field names, keys cannot contain square brackets or points"); + } + bracket = true; + if (i !== first) result.push(str); + str = ""; + } else if (fullName.charAt(i) === "]") { + if (!bracket) { + throw new Error("Field names, keys cannot contain square brackets or points"); + } + bracket = false; } else { - first = Math.min(firstPoint, firstBracket); - } - let str: string = ''; - let bracket: boolean = false; - for (let i = first; i < fullName.length; i++) { - if (fullName.charAt(i) === '.') { - if (bracket) { - throw new Error('Field names, keys cannot contain square brackets or points'); - } - if (i !== first) result.push(str); - str = ''; - } else if (fullName.charAt(i) === '[') { - if (bracket) { - throw new Error('Field names, keys cannot contain square brackets or points'); - } - bracket = true; - if (i !== first) result.push(str); - str = ''; - } else if (fullName.charAt(i) === ']') { - if (!bracket) { - throw new Error('Field names, keys cannot contain square brackets or points'); - } - bracket = false; - } else { - str += fullName.charAt(i); - } + str += fullName.charAt(i); } - if (bracket) { - throw new Error('Invalid name'); - } - result.push(str); - return result.reverse(); + } + if (bracket) { + throw new Error("Invalid name"); + } + result.push(str); + return result.reverse(); } // Get variableName (Without indexes, fields, keys) function getVariableName(fullName: string): string { - let variableName: string = fullName; - if (variableName.indexOf('[') !== -1) { - variableName = variableName.substr(0, variableName.indexOf('[')); - } - if (variableName.indexOf('.') !== -1) { - variableName = variableName.substr(0, variableName.indexOf('.')); - } - return variableName; + let variableName: string = fullName; + if (variableName.indexOf("[") !== -1) { + variableName = variableName.substr(0, variableName.indexOf("[")); + } + if (variableName.indexOf(".") !== -1) { + variableName = variableName.substr(0, variableName.indexOf(".")); + } + return variableName; } // eslint-disable-next-line @typescript-eslint/no-explicit-any async function getValue(address: string, contractName: string, name: string): Promise { - // Mapping from Contract name to fully qualified name known to hardhat - // e.g ZkSync => cache/solpp-generated-contracts/interfaces/IZkSync.sol - const contractMap = {}; - const allContracts = await hre.artifacts.getAllFullyQualifiedNames(); - allContracts.forEach((fullName) => { - const [file, contract] = fullName.split(':'); - contractMap[contract] = { - fullName, - file - }; - }); - if (!(contractName in contractMap)) { - throw new Error(`Unknown contract name, available contracts: ${Object.keys(contractMap)}`); - } - const buildInfo = await hre.artifacts.getBuildInfo(contractMap[contractName].fullName); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const layout = buildInfo.output.contracts[contractMap[contractName].file][contractName].storageLayout; - types = layout.types; - const storage = layout.storage; + // Mapping from Contract name to fully qualified name known to hardhat + // e.g ZkSync => cache/solpp-generated-contracts/interfaces/IZkSync.sol + const contractMap = {}; + const allContracts = await hre.artifacts.getAllFullyQualifiedNames(); + allContracts.forEach((fullName) => { + const [file, contract] = fullName.split(":"); + contractMap[contract] = { + fullName, + file, + }; + }); + if (!(contractName in contractMap)) { + throw new Error(`Unknown contract name, available contracts: ${Object.keys(contractMap)}`); + } + const buildInfo = await hre.artifacts.getBuildInfo(contractMap[contractName].fullName); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const layout = buildInfo.output.contracts[contractMap[contractName].file][contractName].storageLayout; + types = layout.types; + const storage = layout.storage; - const variableName = getVariableName(name); - const params = parseName(name); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let variable: any; + const variableName = getVariableName(name); + const params = parseName(name); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let variable: any; - storage.forEach((node) => { - if (node.label === variableName) variable = node; - }); - if (!variable) { - throw new Error('Invalid name'); - } - return readPartOfVariable(BigNumber.from(variable.slot), variable.offset, address, variable.type, params); + storage.forEach((node) => { + if (node.label === variableName) variable = node; + }); + if (!variable) { + throw new Error("Invalid name"); + } + return readPartOfVariable(BigNumber.from(variable.slot), variable.offset, address, variable.type, params); } async function main() { - const program = new Command(); + const program = new Command(); - program.version('0.1.0').name('read-variable').description('returns value of private and public variables'); + program.version("0.1.0").name("read-variable").description("returns value of private and public variables"); - program - .command('read
') - .option('-f, --file ') - .description('Reads value of variable') - .action(async (address: string, contractName: string, variableName: string) => { - console.log(JSON.stringify(await getValue(address, contractName, variableName), null, 4)); - }); + program + .command("read
") + .option("-f, --file ") + .description("Reads value of variable") + .action(async (address: string, contractName: string, variableName: string) => { + console.log(JSON.stringify(await getValue(address, contractName, variableName), null, 4)); + }); - await program.parseAsync(process.argv); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/revert-reason.ts b/l1-contracts/scripts/revert-reason.ts index 7b932f8ef..5c42a6508 100644 --- a/l1-contracts/scripts/revert-reason.ts +++ b/l1-contracts/scripts/revert-reason.ts @@ -1,112 +1,112 @@ -import * as chalk from 'chalk'; -import { ethers } from 'ethers'; -import { Interface } from 'ethers/lib/utils'; -import * as hardhat from 'hardhat'; -import { web3Url } from './utils'; +import * as chalk from "chalk"; +import { ethers } from "ethers"; +import { Interface } from "ethers/lib/utils"; +import * as hardhat from "hardhat"; +import { web3Url } from "./utils"; -const erc20BridgeInterface = new Interface(hardhat.artifacts.readArtifactSync('L1ERC20Bridge').abi); -const zkSyncInterface = new Interface(hardhat.artifacts.readArtifactSync('IZkSync').abi); -const verifierInterface = new Interface(hardhat.artifacts.readArtifactSync('Verifier').abi); +const erc20BridgeInterface = new Interface(hardhat.artifacts.readArtifactSync("L1ERC20Bridge").abi); +const zkSyncInterface = new Interface(hardhat.artifacts.readArtifactSync("IZkSync").abi); +const verifierInterface = new Interface(hardhat.artifacts.readArtifactSync("Verifier").abi); const interfaces = [erc20BridgeInterface, zkSyncInterface, verifierInterface]; function decodeTransaction(contractInterface, tx) { - try { - return contractInterface.parseTransaction({ data: tx.data }); - } catch { - return null; - } + try { + return contractInterface.parseTransaction({ data: tx.data }); + } catch { + return null; + } } function hex_to_ascii(str1) { - const hex = str1.toString(); - let str = ''; - for (let n = 0; n < hex.length; n += 2) { - str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); - } - return str; + const hex = str1.toString(); + let str = ""; + for (let n = 0; n < hex.length; n += 2) { + str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); + } + return str; } async function reason() { - const args = process.argv.slice(2); - const hash = args[0]; - const web3 = args[1] == null ? web3Url() : args[1]; - console.log('tx hash:', hash); - console.log('provider:', web3); + const args = process.argv.slice(2); + const hash = args[0]; + const web3 = args[1] == null ? web3Url() : args[1]; + console.log("tx hash:", hash); + console.log("provider:", web3); - const provider = new ethers.providers.JsonRpcProvider(web3); + const provider = new ethers.providers.JsonRpcProvider(web3); - const tx = await provider.getTransaction(hash); - tx.gasPrice = null; - if (!tx) { - console.log('tx not found'); - } else { - try { - const parsedTransaction = interfaces - .map((contractInterface) => decodeTransaction(contractInterface, tx)) - .find((tx) => tx != null); + const tx = await provider.getTransaction(hash); + tx.gasPrice = null; + if (!tx) { + console.log("tx not found"); + } else { + try { + const parsedTransaction = interfaces + .map((contractInterface) => decodeTransaction(contractInterface, tx)) + .find((tx) => tx != null); - if (parsedTransaction) { - console.log('parsed tx: ', parsedTransaction.name, parsedTransaction); - console.log('tx args: ', parsedTransaction.name, JSON.stringify(parsedTransaction.args, null, 2)); - } else { - console.log('tx:', tx); - } - } catch (e) { - console.log('zkSync transaction is not parsed'); - } + if (parsedTransaction) { + console.log("parsed tx: ", parsedTransaction.name, parsedTransaction); + console.log("tx args: ", parsedTransaction.name, JSON.stringify(parsedTransaction.args, null, 2)); + } else { + console.log("tx:", tx); + } + } catch (e) { + console.log("zkSync transaction is not parsed"); + } - const transaction = await provider.getTransaction(hash); - const receipt = await provider.getTransactionReceipt(hash); - console.log('receipt:', receipt); - console.log('\n \n '); + const transaction = await provider.getTransaction(hash); + const receipt = await provider.getTransactionReceipt(hash); + console.log("receipt:", receipt); + console.log("\n \n "); - if (receipt.gasUsed) { - const gasLimit = transaction.gasLimit; - const gasUsed = receipt.gasUsed; - console.log('Gas limit: ', transaction.gasLimit.toString()); - console.log('Gas used: ', receipt.gasUsed.toString()); + if (receipt.gasUsed) { + const gasLimit = transaction.gasLimit; + const gasUsed = receipt.gasUsed; + console.log("Gas limit: ", transaction.gasLimit.toString()); + console.log("Gas used: ", receipt.gasUsed.toString()); - // If more than 90% of gas was used, report it as an error. - const threshold = gasLimit.mul(90).div(100); - if (gasUsed.gte(threshold)) { - const error = chalk.bold.red; - console.log(error('More than 90% of gas limit was used!')); - console.log(error('It may be the reason of the transaction failure')); - } - } + // If more than 90% of gas was used, report it as an error. + const threshold = gasLimit.mul(90).div(100); + if (gasUsed.gte(threshold)) { + const error = chalk.bold.red; + console.log(error("More than 90% of gas limit was used!")); + console.log(error("It may be the reason of the transaction failure")); + } + } - if (receipt.status) { - console.log('tx success'); - } else { - const code = await provider.call(tx, tx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - console.log('revert reason:', reason); - console.log('revert code', code); - } + if (receipt.status) { + console.log("tx success"); + } else { + const code = await provider.call(tx, tx.blockNumber); + const reason = hex_to_ascii(code.substr(138)); + console.log("revert reason:", reason); + console.log("revert code", code); + } - for (const log of receipt.logs) { - console.log(log); - try { - const parsedLog = interfaces - .map((contractInterface) => contractInterface.parseLog(log)) - .find((log) => log != null); + for (const log of receipt.logs) { + console.log(log); + try { + const parsedLog = interfaces + .map((contractInterface) => contractInterface.parseLog(log)) + .find((log) => log != null); - if (parsedLog) { - console.log(parsedLog); - } else { - console.log(log); - } - } catch { - // ignore - } + if (parsedLog) { + console.log(parsedLog); + } else { + console.log(log); } + } catch { + // ignore + } } + } } reason() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/token-info.ts b/l1-contracts/scripts/token-info.ts index bf2af5573..415954d81 100644 --- a/l1-contracts/scripts/token-info.ts +++ b/l1-contracts/scripts/token-info.ts @@ -1,51 +1,51 @@ -import { Command } from 'commander'; -import { ethers } from 'ethers'; -import { web3Provider } from './utils'; +import { Command } from "commander"; +import { ethers } from "ethers"; +import { web3Provider } from "./utils"; const provider = web3Provider(); type Token = { - address: string; - name: string | null; - symbol: string | null; - decimals: number | null; + address: string; + name: string | null; + symbol: string | null; + decimals: number | null; }; const TokenInterface = [ - 'function name() view returns (string)', - 'function symbol() view returns (string)', - 'function decimals() view returns (uint)' + "function name() view returns (string)", + "function symbol() view returns (string)", + "function decimals() view returns (uint)", ]; async function tokenInfo(address: string): Promise { - const contract = new ethers.Contract(address, TokenInterface, provider); - - return { - address: address, - name: await contract.name().catch(() => null), - symbol: await contract.symbol().catch(() => null), - decimals: await contract - .decimals() - .then((decimals) => Number(decimals)) - .catch(() => null) - }; + const contract = new ethers.Contract(address, TokenInterface, provider); + + return { + address: address, + name: await contract.name().catch(() => null), + symbol: await contract.symbol().catch(() => null), + decimals: await contract + .decimals() + .then((decimals) => Number(decimals)) + .catch(() => null), + }; } async function main() { - const program = new Command(); + const program = new Command(); - program.version('0.1.0').name('token-info').description('deploy testnet erc20 token'); + program.version("0.1.0").name("token-info").description("deploy testnet erc20 token"); - program.command('info
').action(async (address: string) => { - console.log(JSON.stringify(await tokenInfo(address), null, 2)); - }); + program.command("info
").action(async (address: string) => { + console.log(JSON.stringify(await tokenInfo(address), null, 2)); + }); - await program.parseAsync(process.argv); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/upgrades/upgrade-1.ts b/l1-contracts/scripts/upgrades/upgrade-1.ts index 66eb8f298..bc07102ac 100644 --- a/l1-contracts/scripts/upgrades/upgrade-1.ts +++ b/l1-contracts/scripts/upgrades/upgrade-1.ts @@ -1,72 +1,72 @@ -import * as hardhat from 'hardhat'; -import { Command } from 'commander'; -import { deployedAddressesFromEnv } from '../src.ts/deploy'; -import { getNumberFromEnv } from './utils'; -import { diamondCut } from '../src.ts/diamondCut'; -import type { BigNumberish, BytesLike } from 'ethers'; -import { ethers } from 'hardhat'; +import * as hardhat from "hardhat"; +import { Command } from "commander"; +import { deployedAddressesFromEnv } from "../src.ts/deploy"; +import { getNumberFromEnv } from "./utils"; +import { diamondCut } from "../src.ts/diamondCut"; +import type { BigNumberish, BytesLike } from "ethers"; +import { ethers } from "hardhat"; type DeploymentPram = { - bytecodeHash: string; - newAddress: string; - value: BigNumberish; - input: string; + bytecodeHash: string; + newAddress: string; + value: BigNumberish; + input: string; }; -const priorityTxMaxGasLimit = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); +const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); const ZERO_ADDRESS = ethers.constants.AddressZero; async function main() { - const program = new Command(); + const program = new Command(); - program.version('0.1.0').name('upgrade-l2-force-deploy'); + program.version("0.1.0").name("upgrade-l2-force-deploy"); - program - .command('prepare-upgrade-params ') - .option('--factory-deps ') - .action(async (deploymentParams: string, cmd) => { - // Get deployed L1 contract addresses from environment variables and interfaces for them - const l1Contracts = deployedAddressesFromEnv(); + program + .command("prepare-upgrade-params ") + .option("--factory-deps ") + .action(async (deploymentParams: string, cmd) => { + // Get deployed L1 contract addresses from environment variables and interfaces for them + const l1Contracts = deployedAddressesFromEnv(); - const diamondUpgradeInit = await hardhat.ethers.getContractAt('DiamondUpgradeInit', ZERO_ADDRESS); - const diamondCutFacet = await hardhat.ethers.getContractAt('DiamondCutFacet', ZERO_ADDRESS); - const l2Deployer = await hardhat.ethers.getContractAt('IContractDeployer', ZERO_ADDRESS); + const diamondUpgradeInit = await hardhat.ethers.getContractAt("DiamondUpgradeInit", ZERO_ADDRESS); + const diamondCutFacet = await hardhat.ethers.getContractAt("DiamondCutFacet", ZERO_ADDRESS); + const l2Deployer = await hardhat.ethers.getContractAt("IContractDeployer", ZERO_ADDRESS); - // Encode data for the upgrade call - const params: Array = JSON.parse(deploymentParams); - const encodedParams = await l2Deployer.interface.encodeFunctionData('forceDeployOnAddresses', [params]); - const factoryDeps: Array = cmd.factoryDeps ? JSON.parse(cmd.factoryDeps) : []; + // Encode data for the upgrade call + const params: Array = JSON.parse(deploymentParams); + const encodedParams = await l2Deployer.interface.encodeFunctionData("forceDeployOnAddresses", [params]); + const factoryDeps: Array = cmd.factoryDeps ? JSON.parse(cmd.factoryDeps) : []; - // Prepare the diamond cut data - const upgradeInitData = await diamondUpgradeInit.interface.encodeFunctionData('forceDeployL2Contract', [ - encodedParams, - factoryDeps, - priorityTxMaxGasLimit - ]); - const upgradeParam = diamondCut([], l1Contracts.ZkSync.DiamondUpgradeInit, upgradeInitData); + // Prepare the diamond cut data + const upgradeInitData = await diamondUpgradeInit.interface.encodeFunctionData("forceDeployL2Contract", [ + encodedParams, + factoryDeps, + priorityTxMaxGasLimit, + ]); + const upgradeParam = diamondCut([], l1Contracts.ZkSync.DiamondUpgradeInit, upgradeInitData); - // Get transaction data of the `proposeDiamondCut` - const proposeDiamondCut = await diamondCutFacet.interface.encodeFunctionData('proposeDiamondCut', [ - upgradeParam.facetCuts, - upgradeParam.initAddress - ]); + // Get transaction data of the `proposeDiamondCut` + const proposeDiamondCut = await diamondCutFacet.interface.encodeFunctionData("proposeDiamondCut", [ + upgradeParam.facetCuts, + upgradeParam.initAddress, + ]); - // Get transaction data of the `executeDiamondCutProposal` - const executeDiamondCutProposal = await diamondCutFacet.interface.encodeFunctionData( - 'executeDiamondCutProposal', - [upgradeParam] - ); + // Get transaction data of the `executeDiamondCutProposal` + const executeDiamondCutProposal = await diamondCutFacet.interface.encodeFunctionData( + "executeDiamondCutProposal", + [upgradeParam] + ); - console.log(`proposeDiamondCut\n${proposeDiamondCut}\n`); - console.log(`executeDiamondCutProposal\n${executeDiamondCutProposal}\n`); - }); + console.log(`proposeDiamondCut\n${proposeDiamondCut}\n`); + console.log(`executeDiamondCutProposal\n${executeDiamondCutProposal}\n`); + }); - await program.parseAsync(process.argv); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/upgrades/upgrade-2.ts b/l1-contracts/scripts/upgrades/upgrade-2.ts index 0a6912898..2da1f120f 100644 --- a/l1-contracts/scripts/upgrades/upgrade-2.ts +++ b/l1-contracts/scripts/upgrades/upgrade-2.ts @@ -1,145 +1,143 @@ -import { Command } from 'commander'; -import { diamondCut } from '../src.ts/diamondCut'; -import type { BigNumberish } from 'ethers'; -import { Wallet } from 'ethers'; -import { ethers } from 'hardhat'; -import { Provider } from 'zksync-web3'; -import '@nomiclabs/hardhat-ethers'; -import { web3Provider } from './utils'; -import { Deployer } from '../src.ts/deploy'; -import * as fs from 'fs'; -import * as path from 'path'; +import { Command } from "commander"; +import { diamondCut } from "../src.ts/diamondCut"; +import type { BigNumberish } from "ethers"; +import { Wallet } from "ethers"; +import { ethers } from "hardhat"; +import { Provider } from "zksync-web3"; +import "@nomiclabs/hardhat-ethers"; +import { web3Provider } from "./utils"; +import { Deployer } from "../src.ts/deploy"; +import * as fs from "fs"; +import * as path from "path"; type ForceDeployment = { - bytecodeHash: string; - newAddress: string; - value: BigNumberish; - input: string; + bytecodeHash: string; + newAddress: string; + value: BigNumberish; + input: string; }; function sleep(millis: number) { - return new Promise((resolve) => setTimeout(resolve, millis)); + return new Promise((resolve) => setTimeout(resolve, millis)); } async function prepareCalldata( - diamondUpgradeAddress: string, - deployerBytecodeHash: string, - params: Array + diamondUpgradeAddress: string, + deployerBytecodeHash: string, + params: Array ) { - const DiamondUpgradeInit2 = await ethers.getContractAt('DiamondUpgradeInit2', ZERO_ADDRESS); - const oldDeployerSystemContract = await ethers.getContractAt('IOldContractDeployer', ZERO_ADDRESS); - const newDeployerSystemContract = await ethers.getContractAt('IContractDeployer', ZERO_ADDRESS); - - const upgradeDeployerCalldata = await oldDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddress', - [deployerBytecodeHash, DEPLOYER_SYSTEM_CONTRACT_ADDRESS, '0x'] - ); - const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddresses', - [params] - ); - - // Prepare the diamond cut data - const upgradeInitData = await DiamondUpgradeInit2.interface.encodeFunctionData('forceDeploy2', [ - upgradeDeployerCalldata, - upgradeSystemContractsCalldata, - [] // Empty factory deps - ]); - - return diamondCut([], diamondUpgradeAddress, upgradeInitData); + const DiamondUpgradeInit2 = await ethers.getContractAt("DiamondUpgradeInit2", ZERO_ADDRESS); + const oldDeployerSystemContract = await ethers.getContractAt("IOldContractDeployer", ZERO_ADDRESS); + const newDeployerSystemContract = await ethers.getContractAt("IContractDeployer", ZERO_ADDRESS); + + const upgradeDeployerCalldata = await oldDeployerSystemContract.interface.encodeFunctionData("forceDeployOnAddress", [ + deployerBytecodeHash, + DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + "0x", + ]); + const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( + "forceDeployOnAddresses", + [params] + ); + + // Prepare the diamond cut data + const upgradeInitData = await DiamondUpgradeInit2.interface.encodeFunctionData("forceDeploy2", [ + upgradeDeployerCalldata, + upgradeSystemContractsCalldata, + [], // Empty factory deps + ]); + + return diamondCut([], diamondUpgradeAddress, upgradeInitData); } const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); const ZERO_ADDRESS = ethers.constants.AddressZero; -const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008006'; +const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; async function main() { - const program = new Command(); - - program.version('0.1.0').name('force-deploy-upgrade-2'); - - program - .command('prepare-calldata') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--new-deployer-bytecodehash ') - .requiredOption('--deployment-params ') - .action(async (cmd) => { - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Get new deployer bytecodehash - const deployerBytecodeHash = cmd.newDeployerBytecodehash; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - // Get diamond cut data - const calldata = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); - console.log(calldata); - }); - - program - .command('force-upgrade') - .option('--private-key ') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--new-deployer-bytecodehash ') - .requiredOption('--deployment-params ') - .action(async (cmd) => { - const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - - const deployer = new Deployer({ - deployWallet, - governorAddress: ZERO_ADDRESS, - verbose: true - }); - const zkSyncContract = deployer.zkSyncContract(deployWallet); - - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Get new deployer bytecodehash - const deployerBytecodeHash = cmd.newDeployerBytecodehash; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - // Get diamond cut data - const upgradeParam = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); - - const proposeUpgradeTx = await zkSyncContract.proposeDiamondCut( - upgradeParam.facetCuts, - upgradeParam.initAddress - ); - await proposeUpgradeTx.wait(); - - const executeUpgradeTx = await zkSyncContract.executeDiamondCutProposal(upgradeParam); - const executeUpgradeRec = await executeUpgradeTx.wait(); - const deployL2TxHashes = executeUpgradeRec.events - .filter((event) => event.event === 'NewPriorityRequest') - .map((event) => event.args[1]); - for (const txHash of deployL2TxHashes) { - console.log(txHash); - let receipt = null; - while (receipt == null) { - receipt = await zksProvider.getTransactionReceipt(txHash); - await sleep(100); - } - - if (receipt.status != 1) { - throw new Error('Failed to process L2 tx'); - } - } - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("force-deploy-upgrade-2"); + + program + .command("prepare-calldata") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--new-deployer-bytecodehash ") + .requiredOption("--deployment-params ") + .action(async (cmd) => { + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Get new deployer bytecodehash + const deployerBytecodeHash = cmd.newDeployerBytecodehash; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + // Get diamond cut data + const calldata = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); + console.log(calldata); + }); + + program + .command("force-upgrade") + .option("--private-key ") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--new-deployer-bytecodehash ") + .requiredOption("--deployment-params ") + .action(async (cmd) => { + const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + + const deployer = new Deployer({ + deployWallet, + governorAddress: ZERO_ADDRESS, + verbose: true, + }); + const zkSyncContract = deployer.zkSyncContract(deployWallet); + + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Get new deployer bytecodehash + const deployerBytecodeHash = cmd.newDeployerBytecodehash; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + // Get diamond cut data + const upgradeParam = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); + + const proposeUpgradeTx = await zkSyncContract.proposeDiamondCut(upgradeParam.facetCuts, upgradeParam.initAddress); + await proposeUpgradeTx.wait(); + + const executeUpgradeTx = await zkSyncContract.executeDiamondCutProposal(upgradeParam); + const executeUpgradeRec = await executeUpgradeTx.wait(); + const deployL2TxHashes = executeUpgradeRec.events + .filter((event) => event.event === "NewPriorityRequest") + .map((event) => event.args[1]); + for (const txHash of deployL2TxHashes) { + console.log(txHash); + let receipt = null; + while (receipt == null) { + receipt = await zksProvider.getTransactionReceipt(txHash); + await sleep(100); + } + + if (receipt.status != 1) { + throw new Error("Failed to process L2 tx"); + } + } + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/upgrades/upgrade-3.ts b/l1-contracts/scripts/upgrades/upgrade-3.ts index 81d69cc0e..f6ddf328e 100644 --- a/l1-contracts/scripts/upgrades/upgrade-3.ts +++ b/l1-contracts/scripts/upgrades/upgrade-3.ts @@ -1,140 +1,132 @@ -import { Command } from 'commander'; -import { diamondCut } from '../../src.ts/diamondCut'; -import { Wallet } from 'ethers'; -import { ethers } from 'hardhat'; -import { Provider } from 'zksync-web3'; -import '@nomiclabs/hardhat-ethers'; -import { web3Provider } from '../utils'; -import { Deployer } from '../../src.ts/deploy'; -import * as fs from 'fs'; -import * as path from 'path'; -import { IOldDiamondCutFactory } from '../../typechain/IOldDiamondCutFactory'; +import { Command } from "commander"; +import { diamondCut } from "../../src.ts/diamondCut"; +import { Wallet } from "ethers"; +import { ethers } from "hardhat"; +import { Provider } from "zksync-web3"; +import "@nomiclabs/hardhat-ethers"; +import { web3Provider } from "../utils"; +import { Deployer } from "../../src.ts/deploy"; +import * as fs from "fs"; +import * as path from "path"; +import { IOldDiamondCutFactory } from "../../typechain/IOldDiamondCutFactory"; function sleep(millis: number) { - return new Promise((resolve) => setTimeout(resolve, millis)); + return new Promise((resolve) => setTimeout(resolve, millis)); } async function prepareCalldata( - diamondUpgradeAddress: string, - allowlist: string, - verifier: string, - prioirityTxMaxGasLimit: string + diamondUpgradeAddress: string, + allowlist: string, + verifier: string, + prioirityTxMaxGasLimit: string ) { - const DiamondUpgradeInit3 = await ethers.getContractAt('DiamondUpgradeInit3', ZERO_ADDRESS); + const DiamondUpgradeInit3 = await ethers.getContractAt("DiamondUpgradeInit3", ZERO_ADDRESS); - // Prepare the diamond cut data - const upgradeInitData = await DiamondUpgradeInit3.interface.encodeFunctionData('upgrade', [ - allowlist, - verifier, - prioirityTxMaxGasLimit - ]); + // Prepare the diamond cut data + const upgradeInitData = await DiamondUpgradeInit3.interface.encodeFunctionData("upgrade", [ + allowlist, + verifier, + prioirityTxMaxGasLimit, + ]); - return diamondCut([], diamondUpgradeAddress, upgradeInitData); + return diamondCut([], diamondUpgradeAddress, upgradeInitData); } const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); const ZERO_ADDRESS = ethers.constants.AddressZero; async function main() { - const program = new Command(); - - program.version('0.1.0').name('force-deploy-upgrade-2'); - - program - .command('prepare-calldata') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--allowlist-address ') - .requiredOption('--verifier-address ') - .requiredOption('--prioirity-tx-max-gas-limit ') - .action(async (cmd) => { - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Get address of the allowlist contract - const allowlist = cmd.allowlistAddress; - // Get address of the verifier contract - const verifier = cmd.verifierAddress; - // Get the prioirity tx max L2 gas limit - const prioirityTxMaxGasLimit = cmd.prioirityTxMaxGasLimit; - // Get diamond cut data - const calldata = await prepareCalldata(diamondUpgradeAddress, allowlist, verifier, prioirityTxMaxGasLimit); - console.log(calldata); - }); - - program - .command('force-upgrade') - .option('--private-key ') - .option('--proposal-id ') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--allowlist-address ') - .requiredOption('--verifier-address ') - .requiredOption('--prioirity-tx-max-gas-limit ') - .action(async (cmd) => { - const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - - const deployer = new Deployer({ - deployWallet, - governorAddress: ZERO_ADDRESS, - verbose: true - }); - - const zkSyncContract = IOldDiamondCutFactory.connect(deployer.addresses.ZkSync.DiamondProxy, deployWallet); - - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Get address of the allowlist contract - const allowlist = cmd.allowlistAddress; - // Get address of the verifier contract - const verifier = cmd.verifierAddress; - // Get the prioirity tx max L2 gas limit - const prioirityTxMaxGasLimit = cmd.prioirityTxMaxGasLimit; - // Get diamond cut data - const upgradeParam = await prepareCalldata( - diamondUpgradeAddress, - allowlist, - verifier, - prioirityTxMaxGasLimit - ); - - const proposeUpgradeTx = await zkSyncContract.proposeDiamondCut( - upgradeParam.facetCuts, - upgradeParam.initAddress - ); - await proposeUpgradeTx.wait(); - - const executeUpgradeTx = await zkSyncContract.executeDiamondCutProposal(upgradeParam); - const executeUpgradeRec = await executeUpgradeTx.wait(); - const deployL2TxHashes = executeUpgradeRec.events - .filter((event) => event.event === 'NewPriorityRequest') - .map((event) => event.args[1]); - for (const txHash of deployL2TxHashes) { - console.log(txHash); - let receipt = null; - while (receipt == null) { - receipt = await zksProvider.getTransactionReceipt(txHash); - await sleep(100); - } - - if (receipt.status != 1) { - throw new Error('Failed to process L2 tx'); - } - } - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("force-deploy-upgrade-2"); + + program + .command("prepare-calldata") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--allowlist-address ") + .requiredOption("--verifier-address ") + .requiredOption("--prioirity-tx-max-gas-limit ") + .action(async (cmd) => { + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Get address of the allowlist contract + const allowlist = cmd.allowlistAddress; + // Get address of the verifier contract + const verifier = cmd.verifierAddress; + // Get the prioirity tx max L2 gas limit + const prioirityTxMaxGasLimit = cmd.prioirityTxMaxGasLimit; + // Get diamond cut data + const calldata = await prepareCalldata(diamondUpgradeAddress, allowlist, verifier, prioirityTxMaxGasLimit); + console.log(calldata); + }); + + program + .command("force-upgrade") + .option("--private-key ") + .option("--proposal-id ") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--allowlist-address ") + .requiredOption("--verifier-address ") + .requiredOption("--prioirity-tx-max-gas-limit ") + .action(async (cmd) => { + const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + + const deployer = new Deployer({ + deployWallet, + governorAddress: ZERO_ADDRESS, + verbose: true, + }); + + const zkSyncContract = IOldDiamondCutFactory.connect(deployer.addresses.ZkSync.DiamondProxy, deployWallet); + + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Get address of the allowlist contract + const allowlist = cmd.allowlistAddress; + // Get address of the verifier contract + const verifier = cmd.verifierAddress; + // Get the prioirity tx max L2 gas limit + const prioirityTxMaxGasLimit = cmd.prioirityTxMaxGasLimit; + // Get diamond cut data + const upgradeParam = await prepareCalldata(diamondUpgradeAddress, allowlist, verifier, prioirityTxMaxGasLimit); + + const proposeUpgradeTx = await zkSyncContract.proposeDiamondCut(upgradeParam.facetCuts, upgradeParam.initAddress); + await proposeUpgradeTx.wait(); + + const executeUpgradeTx = await zkSyncContract.executeDiamondCutProposal(upgradeParam); + const executeUpgradeRec = await executeUpgradeTx.wait(); + const deployL2TxHashes = executeUpgradeRec.events + .filter((event) => event.event === "NewPriorityRequest") + .map((event) => event.args[1]); + for (const txHash of deployL2TxHashes) { + console.log(txHash); + let receipt = null; + while (receipt == null) { + receipt = await zksProvider.getTransactionReceipt(txHash); + await sleep(100); + } + + if (receipt.status != 1) { + throw new Error("Failed to process L2 tx"); + } + } + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/upgrades/upgrade-4.ts b/l1-contracts/scripts/upgrades/upgrade-4.ts index 1d036d3f5..4ad01a17e 100644 --- a/l1-contracts/scripts/upgrades/upgrade-4.ts +++ b/l1-contracts/scripts/upgrades/upgrade-4.ts @@ -1,161 +1,161 @@ -import { Command } from 'commander'; -import { diamondCut } from '../../src.ts/diamondCut'; -import type { BigNumberish } from 'ethers'; -import { Wallet } from 'ethers'; -import { ethers } from 'hardhat'; -import { Provider } from 'zksync-web3'; -import '@nomiclabs/hardhat-ethers'; -import { web3Provider } from '../utils'; -import { Deployer } from '../../src.ts/deploy'; -import * as fs from 'fs'; -import * as path from 'path'; +import { Command } from "commander"; +import { diamondCut } from "../../src.ts/diamondCut"; +import type { BigNumberish } from "ethers"; +import { Wallet } from "ethers"; +import { ethers } from "hardhat"; +import { Provider } from "zksync-web3"; +import "@nomiclabs/hardhat-ethers"; +import { web3Provider } from "../utils"; +import { Deployer } from "../../src.ts/deploy"; +import * as fs from "fs"; +import * as path from "path"; type ForceDeploymentOld = { - bytecodeHash: string; - newAddress: string; - value: BigNumberish; - input: string; + bytecodeHash: string; + newAddress: string; + value: BigNumberish; + input: string; }; type ForceDeployment = { - bytecodeHash: string; - newAddress: string; - callConstructor: boolean; - value: BigNumberish; - input: string; + bytecodeHash: string; + newAddress: string; + callConstructor: boolean; + value: BigNumberish; + input: string; }; function sleep(millis: number) { - return new Promise((resolve) => setTimeout(resolve, millis)); + return new Promise((resolve) => setTimeout(resolve, millis)); } async function prepareCalldata( - diamondUpgradeAddress: string, - deployerBytecodeHash: string, - params: Array + diamondUpgradeAddress: string, + deployerBytecodeHash: string, + params: Array ) { - const DiamondUpgradeInit4 = await ethers.getContractAt('DiamondUpgradeInit4', ZERO_ADDRESS); - const oldDeployerSystemContract = await ethers.getContractAt( - 'cache/solpp-generated-contracts/zksync/upgrade-initializers/DiamondUpgradeInit4.sol:IOldContractDeployer', - ZERO_ADDRESS - ); - const newDeployerSystemContract = await ethers.getContractAt('IContractDeployer', ZERO_ADDRESS); - - const upgradeDeployerParams: ForceDeploymentOld = { - bytecodeHash: deployerBytecodeHash, - newAddress: DEPLOYER_SYSTEM_CONTRACT_ADDRESS, - value: 0, - input: '0x' - }; - const upgradeDeployerCalldata = await oldDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddresses', - [[upgradeDeployerParams]] - ); - const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddresses', - [params] - ); - - // Prepare the diamond cut data - const upgradeInitData = await DiamondUpgradeInit4.interface.encodeFunctionData('forceDeploy2', [ - upgradeDeployerCalldata, - upgradeSystemContractsCalldata, - [] // Empty factory deps - ]); - - return diamondCut([], diamondUpgradeAddress, upgradeInitData); + const DiamondUpgradeInit4 = await ethers.getContractAt("DiamondUpgradeInit4", ZERO_ADDRESS); + const oldDeployerSystemContract = await ethers.getContractAt( + "cache/solpp-generated-contracts/zksync/upgrade-initializers/DiamondUpgradeInit4.sol:IOldContractDeployer", + ZERO_ADDRESS + ); + const newDeployerSystemContract = await ethers.getContractAt("IContractDeployer", ZERO_ADDRESS); + + const upgradeDeployerParams: ForceDeploymentOld = { + bytecodeHash: deployerBytecodeHash, + newAddress: DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + value: 0, + input: "0x", + }; + const upgradeDeployerCalldata = await oldDeployerSystemContract.interface.encodeFunctionData( + "forceDeployOnAddresses", + [[upgradeDeployerParams]] + ); + const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( + "forceDeployOnAddresses", + [params] + ); + + // Prepare the diamond cut data + const upgradeInitData = await DiamondUpgradeInit4.interface.encodeFunctionData("forceDeploy2", [ + upgradeDeployerCalldata, + upgradeSystemContractsCalldata, + [], // Empty factory deps + ]); + + return diamondCut([], diamondUpgradeAddress, upgradeInitData); } const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); const ZERO_ADDRESS = ethers.constants.AddressZero; -const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008006'; +const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; async function main() { - const program = new Command(); - - program.version('0.1.0').name('force-deploy-upgrade-2'); - - program - .command('prepare-calldata') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--new-deployer-bytecodehash ') - .requiredOption('--deployment-params ') - .action(async (cmd) => { - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Get new deployer bytecodehash - const deployerBytecodeHash = cmd.newDeployerBytecodehash; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - // Get diamond cut data - const calldata = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); - console.log(calldata); - }); - - program - .command('force-upgrade') - .option('--private-key ') - .option('--proposal-id ') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--new-deployer-bytecodehash ') - .requiredOption('--deployment-params ') - .action(async (cmd) => { - const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - - const deployer = new Deployer({ - deployWallet, - governorAddress: ZERO_ADDRESS, - verbose: true - }); - const zkSyncContract = deployer.zkSyncContract(deployWallet); - - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Get new deployer bytecodehash - const deployerBytecodeHash = cmd.newDeployerBytecodehash; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - // Get diamond cut data - const upgradeParam = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); - - const proposalId = cmd.proposalId ? cmd.proposalId : await zkSyncContract.getCurrentProposalId(); - const proposeUpgradeTx = await zkSyncContract.proposeTransparentUpgrade(upgradeParam, proposalId); - await proposeUpgradeTx.wait(); - - const executeUpgradeTx = await zkSyncContract.executeUpgrade(upgradeParam, ethers.constants.HashZero); - const executeUpgradeRec = await executeUpgradeTx.wait(); - const deployL2TxHashes = executeUpgradeRec.events - .filter((event) => event.event === 'NewPriorityRequest') - .map((event) => event.args[1]); - for (const txHash of deployL2TxHashes) { - console.log(txHash); - let receipt = null; - while (receipt == null) { - receipt = await zksProvider.getTransactionReceipt(txHash); - await sleep(100); - } - - if (receipt.status != 1) { - throw new Error('Failed to process L2 tx'); - } - } - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("force-deploy-upgrade-2"); + + program + .command("prepare-calldata") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--new-deployer-bytecodehash ") + .requiredOption("--deployment-params ") + .action(async (cmd) => { + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Get new deployer bytecodehash + const deployerBytecodeHash = cmd.newDeployerBytecodehash; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + // Get diamond cut data + const calldata = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); + console.log(calldata); + }); + + program + .command("force-upgrade") + .option("--private-key ") + .option("--proposal-id ") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--new-deployer-bytecodehash ") + .requiredOption("--deployment-params ") + .action(async (cmd) => { + const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + + const deployer = new Deployer({ + deployWallet, + governorAddress: ZERO_ADDRESS, + verbose: true, + }); + const zkSyncContract = deployer.zkSyncContract(deployWallet); + + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Get new deployer bytecodehash + const deployerBytecodeHash = cmd.newDeployerBytecodehash; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + // Get diamond cut data + const upgradeParam = await prepareCalldata(diamondUpgradeAddress, deployerBytecodeHash, params); + + const proposalId = cmd.proposalId ? cmd.proposalId : await zkSyncContract.getCurrentProposalId(); + const proposeUpgradeTx = await zkSyncContract.proposeTransparentUpgrade(upgradeParam, proposalId); + await proposeUpgradeTx.wait(); + + const executeUpgradeTx = await zkSyncContract.executeUpgrade(upgradeParam, ethers.constants.HashZero); + const executeUpgradeRec = await executeUpgradeTx.wait(); + const deployL2TxHashes = executeUpgradeRec.events + .filter((event) => event.event === "NewPriorityRequest") + .map((event) => event.args[1]); + for (const txHash of deployL2TxHashes) { + console.log(txHash); + let receipt = null; + while (receipt == null) { + receipt = await zksProvider.getTransactionReceipt(txHash); + await sleep(100); + } + + if (receipt.status != 1) { + throw new Error("Failed to process L2 tx"); + } + } + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/upgrades/upgrade-5.ts b/l1-contracts/scripts/upgrades/upgrade-5.ts index 7cba0b43b..294c089c1 100644 --- a/l1-contracts/scripts/upgrades/upgrade-5.ts +++ b/l1-contracts/scripts/upgrades/upgrade-5.ts @@ -1,163 +1,163 @@ -import { Command } from 'commander'; -import { diamondCut } from '../../src.ts/diamondCut'; -import type { BigNumberish } from 'ethers'; -import { Wallet } from 'ethers'; -import { ethers } from 'hardhat'; -import { Provider } from 'zksync-web3'; -import '@nomiclabs/hardhat-ethers'; -import { web3Provider } from '../utils'; -import { Deployer } from '../../src.ts/deploy'; -import * as fs from 'fs'; -import * as path from 'path'; +import { Command } from "commander"; +import { diamondCut } from "../../src.ts/diamondCut"; +import type { BigNumberish } from "ethers"; +import { Wallet } from "ethers"; +import { ethers } from "hardhat"; +import { Provider } from "zksync-web3"; +import "@nomiclabs/hardhat-ethers"; +import { web3Provider } from "../utils"; +import { Deployer } from "../../src.ts/deploy"; +import * as fs from "fs"; +import * as path from "path"; type ForceDeployment = { - bytecodeHash: string; - newAddress: string; - callConstructor: boolean; - value: BigNumberish; - input: string; + bytecodeHash: string; + newAddress: string; + callConstructor: boolean; + value: BigNumberish; + input: string; }; function sleep(millis: number) { - return new Promise((resolve) => setTimeout(resolve, millis)); + return new Promise((resolve) => setTimeout(resolve, millis)); } async function prepareCalldata( - diamondUpgradeAddress: string, - deployerDeployment: ForceDeployment, - otherDeployments: Array + diamondUpgradeAddress: string, + deployerDeployment: ForceDeployment, + otherDeployments: Array ) { - const DiamondUpgradeInit5 = await ethers.getContractAt('DiamondUpgradeInit5', ZERO_ADDRESS); - const newDeployerSystemContract = await ethers.getContractAt('IL2ContractDeployer', ZERO_ADDRESS); - - const deployerUpgradeCalldata = await newDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddresses', - [[deployerDeployment]] - ); - - const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddresses', - [otherDeployments] - ); - - // Prepare the diamond cut data - const upgradeInitData = await DiamondUpgradeInit5.interface.encodeFunctionData('forceDeploy', [ - deployerUpgradeCalldata, - upgradeSystemContractsCalldata, - [] // Empty factory deps - ]); - - return diamondCut([], diamondUpgradeAddress, upgradeInitData); + const DiamondUpgradeInit5 = await ethers.getContractAt("DiamondUpgradeInit5", ZERO_ADDRESS); + const newDeployerSystemContract = await ethers.getContractAt("IL2ContractDeployer", ZERO_ADDRESS); + + const deployerUpgradeCalldata = await newDeployerSystemContract.interface.encodeFunctionData( + "forceDeployOnAddresses", + [[deployerDeployment]] + ); + + const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( + "forceDeployOnAddresses", + [otherDeployments] + ); + + // Prepare the diamond cut data + const upgradeInitData = await DiamondUpgradeInit5.interface.encodeFunctionData("forceDeploy", [ + deployerUpgradeCalldata, + upgradeSystemContractsCalldata, + [], // Empty factory deps + ]); + + return diamondCut([], diamondUpgradeAddress, upgradeInitData); } const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); const ZERO_ADDRESS = ethers.constants.AddressZero; -const DEPLOYER_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008006'; +const DEPLOYER_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; async function getCalldata(diamondUpgradeAddress: string, params: ForceDeployment[]) { - const deployerDeployments = params.filter( - (deployment) => deployment.newAddress.toLowerCase() == DEPLOYER_CONTRACT_ADDRESS - ); - if (deployerDeployments.length == 0) { - throw new Error('Deployer contract deployment not found'); - } + const deployerDeployments = params.filter( + (deployment) => deployment.newAddress.toLowerCase() == DEPLOYER_CONTRACT_ADDRESS + ); + if (deployerDeployments.length == 0) { + throw new Error("Deployer contract deployment not found"); + } - if (deployerDeployments.length != 1) { - throw new Error('Multiple deployer contract deployments found'); - } + if (deployerDeployments.length != 1) { + throw new Error("Multiple deployer contract deployments found"); + } - const deployerDeployment = deployerDeployments[0]; + const deployerDeployment = deployerDeployments[0]; - const otherDeployments = params.filter( - (deployment) => deployment.newAddress.toLowerCase() != DEPLOYER_CONTRACT_ADDRESS - ); + const otherDeployments = params.filter( + (deployment) => deployment.newAddress.toLowerCase() != DEPLOYER_CONTRACT_ADDRESS + ); - // Get diamond cut data - return await prepareCalldata(diamondUpgradeAddress, deployerDeployment, otherDeployments); + // Get diamond cut data + return await prepareCalldata(diamondUpgradeAddress, deployerDeployment, otherDeployments); } async function main() { - const program = new Command(); - - program.version('0.1.0').name('force-deploy-upgrade-5'); - - program - .command('prepare-calldata') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--deployment-params ') - .action(async (cmd) => { - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - - // Get diamond cut data - const calldata = await getCalldata(diamondUpgradeAddress, params); - console.log(calldata); - }); - - program - .command('force-upgrade') - .option('--private-key ') - .option('--proposal-id ') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--deployment-params ') - .action(async (cmd) => { - const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - - const deployer = new Deployer({ - deployWallet, - governorAddress: ZERO_ADDRESS, - verbose: true - }); - const zkSyncContract = deployer.zkSyncContract(deployWallet); - - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - - // Get diamond cut data - const upgradeParam = await getCalldata(diamondUpgradeAddress, params); - - const proposalId = cmd.proposalId ? cmd.proposalId : (await zkSyncContract.getCurrentProposalId()).add(1); - const proposeUpgradeTx = await zkSyncContract.proposeTransparentUpgrade(upgradeParam, proposalId); - await proposeUpgradeTx.wait(); - - const executeUpgradeTx = await zkSyncContract.executeUpgrade(upgradeParam, ethers.constants.HashZero); - const executeUpgradeRec = await executeUpgradeTx.wait(); - const deployL2TxHashes = executeUpgradeRec.events - .filter((event) => event.event === 'NewPriorityRequest') - .map((event) => event.args[1]); - for (const txHash of deployL2TxHashes) { - console.log(txHash); - let receipt = null; - while (receipt == null) { - receipt = await zksProvider.getTransactionReceipt(txHash); - await sleep(100); - } - - if (receipt.status != 1) { - throw new Error('Failed to process L2 tx'); - } - } - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("force-deploy-upgrade-5"); + + program + .command("prepare-calldata") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--deployment-params ") + .action(async (cmd) => { + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + + // Get diamond cut data + const calldata = await getCalldata(diamondUpgradeAddress, params); + console.log(calldata); + }); + + program + .command("force-upgrade") + .option("--private-key ") + .option("--proposal-id ") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--deployment-params ") + .action(async (cmd) => { + const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + + const deployer = new Deployer({ + deployWallet, + governorAddress: ZERO_ADDRESS, + verbose: true, + }); + const zkSyncContract = deployer.zkSyncContract(deployWallet); + + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + + // Get diamond cut data + const upgradeParam = await getCalldata(diamondUpgradeAddress, params); + + const proposalId = cmd.proposalId ? cmd.proposalId : (await zkSyncContract.getCurrentProposalId()).add(1); + const proposeUpgradeTx = await zkSyncContract.proposeTransparentUpgrade(upgradeParam, proposalId); + await proposeUpgradeTx.wait(); + + const executeUpgradeTx = await zkSyncContract.executeUpgrade(upgradeParam, ethers.constants.HashZero); + const executeUpgradeRec = await executeUpgradeTx.wait(); + const deployL2TxHashes = executeUpgradeRec.events + .filter((event) => event.event === "NewPriorityRequest") + .map((event) => event.args[1]); + for (const txHash of deployL2TxHashes) { + console.log(txHash); + let receipt = null; + while (receipt == null) { + receipt = await zksProvider.getTransactionReceipt(txHash); + await sleep(100); + } + + if (receipt.status != 1) { + throw new Error("Failed to process L2 tx"); + } + } + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/upgrades/upgrade-6.ts b/l1-contracts/scripts/upgrades/upgrade-6.ts index d8ae22f85..02554c50d 100644 --- a/l1-contracts/scripts/upgrades/upgrade-6.ts +++ b/l1-contracts/scripts/upgrades/upgrade-6.ts @@ -1,194 +1,194 @@ -import { Command } from 'commander'; -import { diamondCut } from '../../src.ts/diamondCut'; -import type { BigNumberish } from 'ethers'; -import { ethers } from 'hardhat'; -import { Provider, Wallet } from 'zksync-web3'; -import '@nomiclabs/hardhat-ethers'; -import { web3Provider } from '../utils'; -import { Deployer } from '../../src.ts/deploy'; -import * as fs from 'fs'; -import * as path from 'path'; -import { applyL1ToL2Alias, hashBytecode } from 'zksync-web3/build/src/utils'; +import { Command } from "commander"; +import { diamondCut } from "../../src.ts/diamondCut"; +import type { BigNumberish } from "ethers"; +import { ethers } from "hardhat"; +import { Provider, Wallet } from "zksync-web3"; +import "@nomiclabs/hardhat-ethers"; +import { web3Provider } from "../utils"; +import { Deployer } from "../../src.ts/deploy"; +import * as fs from "fs"; +import * as path from "path"; +import { applyL1ToL2Alias, hashBytecode } from "zksync-web3/build/src/utils"; type ForceDeployment = { - bytecodeHash: string; - newAddress: string; - callConstructor: boolean; - value: BigNumberish; - input: string; + bytecodeHash: string; + newAddress: string; + callConstructor: boolean; + value: BigNumberish; + input: string; }; function sleep(millis: number) { - return new Promise((resolve) => setTimeout(resolve, millis)); + return new Promise((resolve) => setTimeout(resolve, millis)); } async function prepareCalldata( - diamondUpgradeAddress: string, - l2WethTokenDeployment: ForceDeployment, - otherDeployments: Array + diamondUpgradeAddress: string, + l2WethTokenDeployment: ForceDeployment, + otherDeployments: Array ) { - const diamondUpgradeInit6 = await ethers.getContractAt('DiamondUpgradeInit6', ZERO_ADDRESS); - const newDeployerSystemContract = await ethers.getContractAt('IL2ContractDeployer', ZERO_ADDRESS); - - const upgradeL2WethTokenCalldata = await newDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddresses', - [[l2WethTokenDeployment]] - ); - - const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( - 'forceDeployOnAddresses', - [otherDeployments] - ); - - // Prepare the diamond cut data - const upgradeInitData = await diamondUpgradeInit6.interface.encodeFunctionData('forceDeploy', [ - upgradeL2WethTokenCalldata, - upgradeSystemContractsCalldata, - [] // Empty factory deps - ]); - - return diamondCut([], diamondUpgradeAddress, upgradeInitData); + const diamondUpgradeInit6 = await ethers.getContractAt("DiamondUpgradeInit6", ZERO_ADDRESS); + const newDeployerSystemContract = await ethers.getContractAt("IL2ContractDeployer", ZERO_ADDRESS); + + const upgradeL2WethTokenCalldata = await newDeployerSystemContract.interface.encodeFunctionData( + "forceDeployOnAddresses", + [[l2WethTokenDeployment]] + ); + + const upgradeSystemContractsCalldata = await newDeployerSystemContract.interface.encodeFunctionData( + "forceDeployOnAddresses", + [otherDeployments] + ); + + // Prepare the diamond cut data + const upgradeInitData = await diamondUpgradeInit6.interface.encodeFunctionData("forceDeploy", [ + upgradeL2WethTokenCalldata, + upgradeSystemContractsCalldata, + [], // Empty factory deps + ]); + + return diamondCut([], diamondUpgradeAddress, upgradeInitData); } const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); const ZERO_ADDRESS = ethers.constants.AddressZero; async function getCalldata( - diamondUpgradeAddress: string, - params: ForceDeployment[], - l2WethTokenProxyAddress: string, - l2WethTokenImplAddress: string + diamondUpgradeAddress: string, + params: ForceDeployment[], + l2WethTokenProxyAddress: string, + l2WethTokenImplAddress: string ) { - // Generate wallet with random private key to load main contract governor. - const randomWallet = new Wallet(ethers.utils.randomBytes(32), zksProvider, provider); - let governor = await (await randomWallet.getMainContract()).getGovernor(); - // Apply L1 to L2 mask if needed. - if (ethers.utils.hexDataLength(await provider.getCode(governor)) != 0) { - governor = applyL1ToL2Alias(governor); - } - - // This is TransparentUpgradeable proxy - const constructorInput = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'bytes'], - [l2WethTokenImplAddress, governor, '0x'] - ); - const bytecodeHash = ethers.utils.hexlify(hashBytecode(await zksProvider.getCode(l2WethTokenProxyAddress))); - - const l2WethUpgrade: ForceDeployment = { - newAddress: l2WethTokenProxyAddress, - bytecodeHash, - callConstructor: true, - value: 0, - input: constructorInput - }; - // Get diamond cut data - return await prepareCalldata(diamondUpgradeAddress, l2WethUpgrade, params); + // Generate wallet with random private key to load main contract governor. + const randomWallet = new Wallet(ethers.utils.randomBytes(32), zksProvider, provider); + let governor = await (await randomWallet.getMainContract()).getGovernor(); + // Apply L1 to L2 mask if needed. + if (ethers.utils.hexDataLength(await provider.getCode(governor)) != 0) { + governor = applyL1ToL2Alias(governor); + } + + // This is TransparentUpgradeable proxy + const constructorInput = ethers.utils.defaultAbiCoder.encode( + ["address", "address", "bytes"], + [l2WethTokenImplAddress, governor, "0x"] + ); + const bytecodeHash = ethers.utils.hexlify(hashBytecode(await zksProvider.getCode(l2WethTokenProxyAddress))); + + const l2WethUpgrade: ForceDeployment = { + newAddress: l2WethTokenProxyAddress, + bytecodeHash, + callConstructor: true, + value: 0, + input: constructorInput, + }; + // Get diamond cut data + return await prepareCalldata(diamondUpgradeAddress, l2WethUpgrade, params); } async function main() { - const program = new Command(); - - program.version('0.1.0').name('force-deploy-upgrade-6'); - - program - .command('prepare-calldata') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--deployment-params ') - .requiredOption('--l2WethTokenProxyAddress ') - .requiredOption('--l2WethTokenImplAddress ') - .action(async (cmd) => { - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Get L2 WETH token proxy address - const l2WethTokenProxyAddress = cmd.l2WethTokenProxyAddress; - // Get L2 WETH token implementation address - const l2WethTokenImplAddress = cmd.l2WethTokenImplAddress; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - - // Get diamond cut data - const calldata = await getCalldata( - diamondUpgradeAddress, - params, - l2WethTokenProxyAddress, - l2WethTokenImplAddress - ); - console.log(calldata); - }); - - program - .command('force-upgrade') - .option('--private-key ') - .option('--proposal-id ') - .requiredOption('--diamond-upgrade-address ') - .requiredOption('--deployment-params ') - .requiredOption('--l2WethTokenProxyAddress ') - .requiredOption('--l2WethTokenImplAddress ') - .action(async (cmd) => { - const deployWallet = cmd.privateKey - ? new ethers.Wallet(cmd.privateKey, provider) - : ethers.Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - - const deployer = new Deployer({ - deployWallet, - governorAddress: ZERO_ADDRESS, - verbose: true - }); - const zkSyncContract = deployer.zkSyncContract(deployWallet); - - // Get address of the diamond init contract - const diamondUpgradeAddress = cmd.diamondUpgradeAddress; - // Encode data for the upgrade call - const params: Array = JSON.parse(cmd.deploymentParams); - // Get L2 WETH token proxy address - const l2WethTokenProxyAddress = cmd.l2WethTokenProxyAddress; - // Get L2 WETH token implementation address - const l2WethTokenImplAddress = cmd.l2WethTokenImplAddress; - - // Get diamond cut data - const upgradeParam = await getCalldata( - diamondUpgradeAddress, - params, - l2WethTokenProxyAddress, - l2WethTokenImplAddress - ); - - const proposalId = cmd.proposalId ? cmd.proposalId : (await zkSyncContract.getCurrentProposalId()).add(1); - const proposeUpgradeTx = await zkSyncContract.proposeTransparentUpgrade(upgradeParam, proposalId); - await proposeUpgradeTx.wait(); - - const executeUpgradeTx = await zkSyncContract.executeUpgrade(upgradeParam, ethers.constants.HashZero); - const executeUpgradeRec = await executeUpgradeTx.wait(); - const deployL2TxHashes = executeUpgradeRec.events - .filter((event) => event.event === 'NewPriorityRequest') - .map((event) => event.args[1]); - for (const txHash of deployL2TxHashes) { - console.log(txHash); - let receipt = null; - while (receipt == null) { - receipt = await zksProvider.getTransactionReceipt(txHash); - await sleep(100); - } - - if (receipt.status != 1) { - throw new Error('Failed to process L2 tx'); - } - } - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("force-deploy-upgrade-6"); + + program + .command("prepare-calldata") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--deployment-params ") + .requiredOption("--l2WethTokenProxyAddress ") + .requiredOption("--l2WethTokenImplAddress ") + .action(async (cmd) => { + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Get L2 WETH token proxy address + const l2WethTokenProxyAddress = cmd.l2WethTokenProxyAddress; + // Get L2 WETH token implementation address + const l2WethTokenImplAddress = cmd.l2WethTokenImplAddress; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + + // Get diamond cut data + const calldata = await getCalldata( + diamondUpgradeAddress, + params, + l2WethTokenProxyAddress, + l2WethTokenImplAddress + ); + console.log(calldata); + }); + + program + .command("force-upgrade") + .option("--private-key ") + .option("--proposal-id ") + .requiredOption("--diamond-upgrade-address ") + .requiredOption("--deployment-params ") + .requiredOption("--l2WethTokenProxyAddress ") + .requiredOption("--l2WethTokenImplAddress ") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new ethers.Wallet(cmd.privateKey, provider) + : ethers.Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + + const deployer = new Deployer({ + deployWallet, + governorAddress: ZERO_ADDRESS, + verbose: true, + }); + const zkSyncContract = deployer.zkSyncContract(deployWallet); + + // Get address of the diamond init contract + const diamondUpgradeAddress = cmd.diamondUpgradeAddress; + // Encode data for the upgrade call + const params: Array = JSON.parse(cmd.deploymentParams); + // Get L2 WETH token proxy address + const l2WethTokenProxyAddress = cmd.l2WethTokenProxyAddress; + // Get L2 WETH token implementation address + const l2WethTokenImplAddress = cmd.l2WethTokenImplAddress; + + // Get diamond cut data + const upgradeParam = await getCalldata( + diamondUpgradeAddress, + params, + l2WethTokenProxyAddress, + l2WethTokenImplAddress + ); + + const proposalId = cmd.proposalId ? cmd.proposalId : (await zkSyncContract.getCurrentProposalId()).add(1); + const proposeUpgradeTx = await zkSyncContract.proposeTransparentUpgrade(upgradeParam, proposalId); + await proposeUpgradeTx.wait(); + + const executeUpgradeTx = await zkSyncContract.executeUpgrade(upgradeParam, ethers.constants.HashZero); + const executeUpgradeRec = await executeUpgradeTx.wait(); + const deployL2TxHashes = executeUpgradeRec.events + .filter((event) => event.event === "NewPriorityRequest") + .map((event) => event.args[1]); + for (const txHash of deployL2TxHashes) { + console.log(txHash); + let receipt = null; + while (receipt == null) { + receipt = await zksProvider.getTransactionReceipt(txHash); + await sleep(100); + } + + if (receipt.status != 1) { + throw new Error("Failed to process L2 tx"); + } + } + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/utils.ts b/l1-contracts/scripts/utils.ts index 00cbf2a1f..de25a7e45 100644 --- a/l1-contracts/scripts/utils.ts +++ b/l1-contracts/scripts/utils.ts @@ -1,186 +1,183 @@ -import * as chalk from 'chalk'; -import type { BytesLike } from 'ethers'; -import { ethers } from 'ethers'; -import * as fs from 'fs'; -import * as path from 'path'; +import * as chalk from "chalk"; +import type { BytesLike } from "ethers"; +import { ethers } from "ethers"; +import * as fs from "fs"; +import * as path from "path"; const warning = chalk.bold.yellow; -const CREATE2_PREFIX = ethers.utils.solidityKeccak256(['string'], ['zksyncCreate2']); -export const L1_TO_L2_ALIAS_OFFSET = '0x1111000000000000000000000000000000001111'; +const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate2"]); +export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; // eslint-disable-next-line @typescript-eslint/no-var-requires -export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require('../../SystemConfig.json').REQUIRED_L2_GAS_PRICE_PER_PUBDATA; +export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../SystemConfig.json").REQUIRED_L2_GAS_PRICE_PER_PUBDATA; export interface PermissionToCall { - caller: string; - target: string; - functionName: string; - enable: boolean; + caller: string; + target: string; + functionName: string; + enable: boolean; } export interface AccessMode { - target: string; - mode: number; + target: string; + mode: number; } export function web3Url() { - return process.env.ETH_CLIENT_WEB3_URL.split(',')[0] as string; + return process.env.ETH_CLIENT_WEB3_URL.split(",")[0] as string; } export function web3Provider() { - const provider = new ethers.providers.JsonRpcProvider(web3Url()); + const provider = new ethers.providers.JsonRpcProvider(web3Url()); - // Check that `CHAIN_ETH_NETWORK` variable is set. If not, it's most likely because - // the variable was renamed. As this affects the time to deploy contracts in localhost - // scenario, it surely deserves a warning. - const network = process.env.CHAIN_ETH_NETWORK; - if (!network) { - console.log(warning('Network variable is not set. Check if contracts/scripts/utils.ts is correct')); - } + // Check that `CHAIN_ETH_NETWORK` variable is set. If not, it's most likely because + // the variable was renamed. As this affects the time to deploy contracts in localhost + // scenario, it surely deserves a warning. + const network = process.env.CHAIN_ETH_NETWORK; + if (!network) { + console.log(warning("Network variable is not set. Check if contracts/scripts/utils.ts is correct")); + } - // Short polling interval for local network - if (network === 'localhost') { - provider.pollingInterval = 100; - } + // Short polling interval for local network + if (network === "localhost") { + provider.pollingInterval = 100; + } - return provider; + return provider; } export function getAddressFromEnv(envName: string): string { - const address = process.env[envName]; - if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { - throw new Error(`Incorrect address format hash in ${envName} env: ${address}`); - } - return address; + const address = process.env[envName]; + if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { + throw new Error(`Incorrect address format hash in ${envName} env: ${address}`); + } + return address; } export function getHashFromEnv(envName: string): string { - const hash = process.env[envName]; - if (!/^0x[a-fA-F0-9]{64}$/.test(hash)) { - throw new Error(`Incorrect hash format hash in ${envName} env: ${hash}`); - } - return hash; + const hash = process.env[envName]; + if (!/^0x[a-fA-F0-9]{64}$/.test(hash)) { + throw new Error(`Incorrect hash format hash in ${envName} env: ${hash}`); + } + return hash; } export function getNumberFromEnv(envName: string): string { - const number = process.env[envName]; - if (!/^([1-9]\d*|0)$/.test(number)) { - throw new Error(`Incorrect number format number in ${envName} env: ${number}`); - } - return number; + const number = process.env[envName]; + if (!/^([1-9]\d*|0)$/.test(number)) { + throw new Error(`Incorrect number format number in ${envName} env: ${number}`); + } + return number; } const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); export function applyL1ToL2Alias(address: string): string { - return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); + return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); } export function readBatchBootloaderBytecode() { - const bootloaderPath = path.join( - process.env.ZKSYNC_HOME as string, - 'era-contracts-lambda/system-contracts/bootloader' - ); - return fs.readFileSync(`${bootloaderPath}/build/artifacts/proved_batch.yul/proved_batch.yul.zbin`); + const bootloaderPath = path.join(process.env.ZKSYNC_HOME as string, "era-contracts-lambda/system-contracts/bootloader"); + return fs.readFileSync(`${bootloaderPath}/build/artifacts/proved_batch.yul/proved_batch.yul.zbin`); } export function readSystemContractsBytecode(fileName: string) { - const systemContractsPath = path.join(process.env.ZKSYNC_HOME as string, 'era-contracts-lambda/system-contracts'); - const artifact = fs.readFileSync( - `${systemContractsPath}/artifacts-zk/cache-zk/solpp-generated-contracts/${fileName}.sol/${fileName}.json` - ); - return JSON.parse(artifact.toString()).bytecode; + const systemContractsPath = path.join(process.env.ZKSYNC_HOME as string, "era-contracts-lambda/system-contracts"); + const artifact = fs.readFileSync( + `${systemContractsPath}/artifacts-zk/cache-zk/solpp-generated-contracts/${fileName}.sol/${fileName}.json` + ); + return JSON.parse(artifact.toString()).bytecode; } export function hashL2Bytecode(bytecode: ethers.BytesLike): Uint8Array { - // For getting the consistent length we first convert the bytecode to UInt8Array - const bytecodeAsArray = ethers.utils.arrayify(bytecode); + // For getting the consistent length we first convert the bytecode to UInt8Array + const bytecodeAsArray = ethers.utils.arrayify(bytecode); - if (bytecodeAsArray.length % 32 != 0) { - throw new Error('The bytecode length in bytes must be divisible by 32'); - } + if (bytecodeAsArray.length % 32 != 0) { + throw new Error("The bytecode length in bytes must be divisible by 32"); + } - const hashStr = ethers.utils.sha256(bytecodeAsArray); - const hash = ethers.utils.arrayify(hashStr); + const hashStr = ethers.utils.sha256(bytecodeAsArray); + const hash = ethers.utils.arrayify(hashStr); - // Note that the length of the bytecode - // should be provided in 32-byte words. - const bytecodeLengthInWords = bytecodeAsArray.length / 32; - if (bytecodeLengthInWords % 2 == 0) { - throw new Error('Bytecode length in 32-byte words must be odd'); - } - const bytecodeLength = ethers.utils.arrayify(bytecodeAsArray.length / 32); - if (bytecodeLength.length > 2) { - throw new Error('Bytecode length must be less than 2^16 bytes'); - } - // The bytecode should always take the first 2 bytes of the bytecode hash, - // so we pad it from the left in case the length is smaller than 2 bytes. - const bytecodeLengthPadded = ethers.utils.zeroPad(bytecodeLength, 2); + // Note that the length of the bytecode + // should be provided in 32-byte words. + const bytecodeLengthInWords = bytecodeAsArray.length / 32; + if (bytecodeLengthInWords % 2 == 0) { + throw new Error("Bytecode length in 32-byte words must be odd"); + } + const bytecodeLength = ethers.utils.arrayify(bytecodeAsArray.length / 32); + if (bytecodeLength.length > 2) { + throw new Error("Bytecode length must be less than 2^16 bytes"); + } + // The bytecode should always take the first 2 bytes of the bytecode hash, + // so we pad it from the left in case the length is smaller than 2 bytes. + const bytecodeLengthPadded = ethers.utils.zeroPad(bytecodeLength, 2); - const codeHashVersion = new Uint8Array([1, 0]); - hash.set(codeHashVersion, 0); - hash.set(bytecodeLengthPadded, 2); + const codeHashVersion = new Uint8Array([1, 0]); + hash.set(codeHashVersion, 0); + hash.set(bytecodeLengthPadded, 2); - return hash; + return hash; } export function computeL2Create2Address( - deployWallet: string, - bytecode: BytesLike, - constructorInput: BytesLike, - create2Salt: BytesLike + deployWallet: string, + bytecode: BytesLike, + constructorInput: BytesLike, + create2Salt: BytesLike ) { - const senderBytes = ethers.utils.hexZeroPad(deployWallet, 32); - const bytecodeHash = hashL2Bytecode(bytecode); + const senderBytes = ethers.utils.hexZeroPad(deployWallet, 32); + const bytecodeHash = hashL2Bytecode(bytecode); - const constructorInputHash = ethers.utils.keccak256(constructorInput); + const constructorInputHash = ethers.utils.keccak256(constructorInput); - const data = ethers.utils.keccak256( - ethers.utils.concat([CREATE2_PREFIX, senderBytes, create2Salt, bytecodeHash, constructorInputHash]) - ); + const data = ethers.utils.keccak256( + ethers.utils.concat([CREATE2_PREFIX, senderBytes, create2Salt, bytecodeHash, constructorInputHash]) + ); - return ethers.utils.hexDataSlice(data, 12); + return ethers.utils.hexDataSlice(data, 12); } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function print(name: string, data: any) { - console.log(`${name}:\n`, JSON.stringify(data, null, 4), '\n'); + console.log(`${name}:\n`, JSON.stringify(data, null, 4), "\n"); } export function getLowerCaseAddress(address: string) { - return ethers.utils.getAddress(address).toLowerCase(); + return ethers.utils.getAddress(address).toLowerCase(); } export function permissionToCallComparator(first: PermissionToCall, second: PermissionToCall) { - if (getLowerCaseAddress(first.caller) < getLowerCaseAddress(second.caller)) { - return -1; - } - if (getLowerCaseAddress(first.caller) > getLowerCaseAddress(second.caller)) { - return 1; - } + if (getLowerCaseAddress(first.caller) < getLowerCaseAddress(second.caller)) { + return -1; + } + if (getLowerCaseAddress(first.caller) > getLowerCaseAddress(second.caller)) { + return 1; + } - if (getLowerCaseAddress(first.target) < getLowerCaseAddress(second.target)) { - return -1; - } - if (getLowerCaseAddress(first.target) > getLowerCaseAddress(second.target)) { - return 1; - } + if (getLowerCaseAddress(first.target) < getLowerCaseAddress(second.target)) { + return -1; + } + if (getLowerCaseAddress(first.target) > getLowerCaseAddress(second.target)) { + return 1; + } - return first.functionName.localeCompare(second.functionName); + return first.functionName.localeCompare(second.functionName); } export type L1Token = { - name: string; - symbol: string; - decimals: number; - address: string; + name: string; + symbol: string; + decimals: number; + address: string; }; export function getTokens(network: string): L1Token[] { - const configPath = `${process.env.ZKSYNC_HOME}/etc/tokens/${network}.json`; - return JSON.parse( - fs.readFileSync(configPath, { - encoding: 'utf-8' - }) - ); + const configPath = `${process.env.ZKSYNC_HOME}/etc/tokens/${network}.json`; + return JSON.parse( + fs.readFileSync(configPath, { + encoding: "utf-8", + }) + ); } diff --git a/l1-contracts/scripts/verify.ts b/l1-contracts/scripts/verify.ts index 3b877659f..26d599d90 100644 --- a/l1-contracts/scripts/verify.ts +++ b/l1-contracts/scripts/verify.ts @@ -1,74 +1,74 @@ -import * as hardhat from 'hardhat'; -import { deployedAddressesFromEnv } from '../src.ts/deploy'; +import * as hardhat from "hardhat"; +import { deployedAddressesFromEnv } from "../src.ts/deploy"; // eslint-disable-next-line @typescript-eslint/no-explicit-any function verifyPromise(address: string, constructorArguments?: Array, libraries?: object): Promise { - return new Promise((resolve, reject) => { - hardhat - .run('verify:verify', { address, constructorArguments, libraries }) - .then(() => resolve(`Successfully verified ${address}`)) - .catch((e) => reject(`Failed to verify ${address}\nError: ${e.message}`)); - }); + return new Promise((resolve, reject) => { + hardhat + .run("verify:verify", { address, constructorArguments, libraries }) + .then(() => resolve(`Successfully verified ${address}`)) + .catch((e) => reject(`Failed to verify ${address}\nError: ${e.message}`)); + }); } async function main() { - if (process.env.CHAIN_ETH_NETWORK == 'localhost') { - console.log('Skip contract verification on localhost'); - return; - } - if (!process.env.MISC_ETHERSCAN_API_KEY) { - console.log('Skip contract verification given etherscan api key is missing'); - return; - } - const addresses = deployedAddressesFromEnv(); - const promises = []; + if (process.env.CHAIN_ETH_NETWORK == "localhost") { + console.log("Skip contract verification on localhost"); + return; + } + if (!process.env.MISC_ETHERSCAN_API_KEY) { + console.log("Skip contract verification given etherscan api key is missing"); + return; + } + const addresses = deployedAddressesFromEnv(); + const promises = []; - // Contracts without constructor parameters - for (const address of [ - addresses.ZkSync.GettersFacet, - addresses.ZkSync.DiamondInit, - addresses.ZkSync.AdminFacet, - addresses.ZkSync.MailboxFacet, - addresses.ZkSync.ExecutorFacet, - addresses.ZkSync.Verifier - ]) { - const promise = verifyPromise(address); - promises.push(promise); - } + // Contracts without constructor parameters + for (const address of [ + addresses.ZkSync.GettersFacet, + addresses.ZkSync.DiamondInit, + addresses.ZkSync.AdminFacet, + addresses.ZkSync.MailboxFacet, + addresses.ZkSync.ExecutorFacet, + addresses.ZkSync.Verifier, + ]) { + const promise = verifyPromise(address); + promises.push(promise); + } - // TODO: Restore after switching to hardhat tasks (SMA-1711). - // promises.push(verifyPromise(addresses.AllowList, [governor])); + // TODO: Restore after switching to hardhat tasks (SMA-1711). + // promises.push(verifyPromise(addresses.AllowList, [governor])); - // // Proxy - // { - // // Create dummy deployer to get constructor parameters for diamond proxy - // const deployer = new Deployer({ - // deployWallet: ethers.Wallet.createRandom(), - // governorAddress: governor - // }); + // // Proxy + // { + // // Create dummy deployer to get constructor parameters for diamond proxy + // const deployer = new Deployer({ + // deployWallet: ethers.Wallet.createRandom(), + // governorAddress: governor + // }); - // const chainId = process.env.ETH_CLIENT_CHAIN_ID; - // const constructorArguments = [chainId, await deployer.initialProxyDiamondCut()]; - // const promise = verifyPromise(addresses.ZkSync.DiamondProxy, constructorArguments); - // promises.push(promise); - // } + // const chainId = process.env.ETH_CLIENT_CHAIN_ID; + // const constructorArguments = [chainId, await deployer.initialProxyDiamondCut()]; + // const promise = verifyPromise(addresses.ZkSync.DiamondProxy, constructorArguments); + // promises.push(promise); + // } - // Bridges - const promise = verifyPromise(addresses.Bridges.ERC20BridgeImplementation, [ - addresses.ZkSync.DiamondProxy, - addresses.AllowList - ]); - promises.push(promise); + // Bridges + const promise = verifyPromise(addresses.Bridges.ERC20BridgeImplementation, [ + addresses.ZkSync.DiamondProxy, + addresses.AllowList, + ]); + promises.push(promise); - const messages = await Promise.allSettled(promises); - for (const message of messages) { - console.log(message.status == 'fulfilled' ? message.value : message.reason); - } + const messages = await Promise.allSettled(promises); + for (const message of messages) { + console.log(message.status == "fulfilled" ? message.value : message.reason); + } } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l1-contracts/src.ts/deploy-utils.ts b/l1-contracts/src.ts/deploy-utils.ts index 40837e734..4fd11521a 100644 --- a/l1-contracts/src.ts/deploy-utils.ts +++ b/l1-contracts/src.ts/deploy-utils.ts @@ -1,57 +1,57 @@ -import * as hardhat from 'hardhat'; -import '@nomiclabs/hardhat-ethers'; -import { ethers } from 'ethers'; -import { SingletonFactoryFactory } from '../typechain'; +import * as hardhat from "hardhat"; +import "@nomiclabs/hardhat-ethers"; +import { ethers } from "ethers"; +import { SingletonFactoryFactory } from "../typechain"; export async function deployViaCreate2( - deployWallet: ethers.Wallet, - contractName: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args: any[], - create2Salt: string, - ethTxOptions: ethers.providers.TransactionRequest, - create2FactoryAddress: string, - verbose: boolean = true, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - libraries?: any + deployWallet: ethers.Wallet, + contractName: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any[], + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest, + create2FactoryAddress: string, + verbose: boolean = true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + libraries?: any ): Promise<[string, string]> { - // [address, txHash] - - const log = (msg: string) => { - if (verbose) { - console.log(msg); - } - }; - log(`Deploying ${contractName}`); - - const create2Factory = SingletonFactoryFactory.connect(create2FactoryAddress, deployWallet); - const contractFactory = await hardhat.ethers.getContractFactory(contractName, { - signer: deployWallet, - libraries - }); - const bytecode = contractFactory.getDeployTransaction(...[...args, ethTxOptions]).data; - const expectedAddress = ethers.utils.getCreate2Address( - create2Factory.address, - create2Salt, - ethers.utils.keccak256(bytecode) - ); - - const deployedBytecodeBefore = await deployWallet.provider.getCode(expectedAddress); - if (ethers.utils.hexDataLength(deployedBytecodeBefore) > 0) { - log(`Contract ${contractName} already deployed`); - return [expectedAddress, ethers.constants.HashZero]; - } - - const tx = await create2Factory.deploy(bytecode, create2Salt, ethTxOptions); - const receipt = await tx.wait(2); + // [address, txHash] - const gasUsed = receipt.gasUsed; - log(`${contractName} deployed, gasUsed: ${gasUsed.toString()}`); - - const deployedBytecodeAfter = await deployWallet.provider.getCode(expectedAddress); - if (ethers.utils.hexDataLength(deployedBytecodeAfter) == 0) { - throw new Error('Failed to deploy bytecode via create2 factory'); + const log = (msg: string) => { + if (verbose) { + console.log(msg); } - - return [expectedAddress, tx.hash]; + }; + log(`Deploying ${contractName}`); + + const create2Factory = SingletonFactoryFactory.connect(create2FactoryAddress, deployWallet); + const contractFactory = await hardhat.ethers.getContractFactory(contractName, { + signer: deployWallet, + libraries, + }); + const bytecode = contractFactory.getDeployTransaction(...[...args, ethTxOptions]).data; + const expectedAddress = ethers.utils.getCreate2Address( + create2Factory.address, + create2Salt, + ethers.utils.keccak256(bytecode) + ); + + const deployedBytecodeBefore = await deployWallet.provider.getCode(expectedAddress); + if (ethers.utils.hexDataLength(deployedBytecodeBefore) > 0) { + log(`Contract ${contractName} already deployed`); + return [expectedAddress, ethers.constants.HashZero]; + } + + const tx = await create2Factory.deploy(bytecode, create2Salt, ethTxOptions); + const receipt = await tx.wait(2); + + const gasUsed = receipt.gasUsed; + log(`${contractName} deployed, gasUsed: ${gasUsed.toString()}`); + + const deployedBytecodeAfter = await deployWallet.provider.getCode(expectedAddress); + if (ethers.utils.hexDataLength(deployedBytecodeAfter) == 0) { + throw new Error("Failed to deploy bytecode via create2 factory"); + } + + return [expectedAddress, tx.hash]; } diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 897203b6c..1e9f7c4eb 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -1,529 +1,518 @@ -import * as hardhat from 'hardhat'; -import '@nomiclabs/hardhat-ethers'; - -import type { BigNumberish, providers, Signer, Wallet } from 'ethers'; -import { ethers } from 'ethers'; -import { Interface, hexlify } from 'ethers/lib/utils'; -import { diamondCut, getCurrentFacetCutsForAdd } from './diamondCut'; -import { IZkSyncFactory } from '../typechain/IZkSyncFactory'; -import { L1ERC20BridgeFactory } from '../typechain/L1ERC20BridgeFactory'; -import { L1WethBridgeFactory } from '../typechain/L1WethBridgeFactory'; -import { ValidatorTimelockFactory } from '../typechain/ValidatorTimelockFactory'; -import { SingletonFactoryFactory } from '../typechain/SingletonFactoryFactory'; -import { AllowListFactory } from '../typechain'; -import { TransparentUpgradeableProxyFactory } from '../typechain/TransparentUpgradeableProxyFactory'; +import * as hardhat from "hardhat"; +import "@nomiclabs/hardhat-ethers"; + +import type { BigNumberish, providers, Signer, Wallet } from "ethers"; +import { ethers } from "ethers"; +import { Interface, hexlify } from "ethers/lib/utils"; +import { diamondCut, getCurrentFacetCutsForAdd } from "./diamondCut"; +import { IZkSyncFactory } from "../typechain/IZkSyncFactory"; +import { L1ERC20BridgeFactory } from "../typechain/L1ERC20BridgeFactory"; +import { L1WethBridgeFactory } from "../typechain/L1WethBridgeFactory"; +import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory"; +import { SingletonFactoryFactory } from "../typechain/SingletonFactoryFactory"; +import { AllowListFactory } from "../typechain"; +import { TransparentUpgradeableProxyFactory } from "../typechain/TransparentUpgradeableProxyFactory"; import { - readSystemContractsBytecode, - hashL2Bytecode, - getAddressFromEnv, - getHashFromEnv, - getNumberFromEnv, - readBatchBootloaderBytecode, - getTokens -} from '../scripts/utils'; -import { deployViaCreate2 } from './deploy-utils'; -import { IGovernanceFactory } from '../typechain/IGovernanceFactory'; + readSystemContractsBytecode, + hashL2Bytecode, + getAddressFromEnv, + getHashFromEnv, + getNumberFromEnv, + readBatchBootloaderBytecode, + getTokens, +} from "../scripts/utils"; +import { deployViaCreate2 } from "./deploy-utils"; +import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; const L2_BOOTLOADER_BYTECODE_HASH = hexlify(hashL2Bytecode(readBatchBootloaderBytecode())); -const L2_DEFAULT_ACCOUNT_BYTECODE_HASH = hexlify(hashL2Bytecode(readSystemContractsBytecode('DefaultAccount'))); +const L2_DEFAULT_ACCOUNT_BYTECODE_HASH = hexlify(hashL2Bytecode(readSystemContractsBytecode("DefaultAccount"))); export interface DeployedAddresses { - ZkSync: { - MailboxFacet: string; - AdminFacet: string; - ExecutorFacet: string; - GettersFacet: string; - Verifier: string; - DiamondInit: string; - DiamondUpgradeInit: string; - DefaultUpgrade: string; - DiamondProxy: string; - }; - Bridges: { - ERC20BridgeImplementation: string; - ERC20BridgeProxy: string; - WethBridgeImplementation: string; - WethBridgeProxy: string; - }; - Governance: string; - AllowList: string; - ValidatorTimeLock: string; - Create2Factory: string; + ZkSync: { + MailboxFacet: string; + AdminFacet: string; + ExecutorFacet: string; + GettersFacet: string; + Verifier: string; + DiamondInit: string; + DiamondUpgradeInit: string; + DefaultUpgrade: string; + DiamondProxy: string; + }; + Bridges: { + ERC20BridgeImplementation: string; + ERC20BridgeProxy: string; + WethBridgeImplementation: string; + WethBridgeProxy: string; + }; + Governance: string; + AllowList: string; + ValidatorTimeLock: string; + Create2Factory: string; } export interface DeployerConfig { - deployWallet: Wallet; - ownerAddress?: string; - verbose?: boolean; + deployWallet: Wallet; + ownerAddress?: string; + verbose?: boolean; } export function deployedAddressesFromEnv(): DeployedAddresses { - return { - ZkSync: { - MailboxFacet: getAddressFromEnv('CONTRACTS_MAILBOX_FACET_ADDR'), - AdminFacet: getAddressFromEnv('CONTRACTS_ADMIN_FACET_ADDR'), - ExecutorFacet: getAddressFromEnv('CONTRACTS_EXECUTOR_FACET_ADDR'), - GettersFacet: getAddressFromEnv('CONTRACTS_GETTERS_FACET_ADDR'), - DiamondInit: getAddressFromEnv('CONTRACTS_DIAMOND_INIT_ADDR'), - DiamondUpgradeInit: getAddressFromEnv('CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR'), - DefaultUpgrade: getAddressFromEnv('CONTRACTS_DEFAULT_UPGRADE_ADDR'), - DiamondProxy: getAddressFromEnv('CONTRACTS_DIAMOND_PROXY_ADDR'), - Verifier: getAddressFromEnv('CONTRACTS_VERIFIER_ADDR') - }, - Bridges: { - ERC20BridgeImplementation: getAddressFromEnv('CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR'), - ERC20BridgeProxy: getAddressFromEnv('CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR'), - WethBridgeImplementation: getAddressFromEnv('CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR'), - WethBridgeProxy: getAddressFromEnv('CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR') - }, - AllowList: getAddressFromEnv('CONTRACTS_L1_ALLOW_LIST_ADDR'), - Create2Factory: getAddressFromEnv('CONTRACTS_CREATE2_FACTORY_ADDR'), - ValidatorTimeLock: getAddressFromEnv('CONTRACTS_VALIDATOR_TIMELOCK_ADDR'), - Governance: getAddressFromEnv('CONTRACTS_GOVERNANCE_ADDR') - }; + return { + ZkSync: { + MailboxFacet: getAddressFromEnv("CONTRACTS_MAILBOX_FACET_ADDR"), + AdminFacet: getAddressFromEnv("CONTRACTS_ADMIN_FACET_ADDR"), + ExecutorFacet: getAddressFromEnv("CONTRACTS_EXECUTOR_FACET_ADDR"), + GettersFacet: getAddressFromEnv("CONTRACTS_GETTERS_FACET_ADDR"), + DiamondInit: getAddressFromEnv("CONTRACTS_DIAMOND_INIT_ADDR"), + DiamondUpgradeInit: getAddressFromEnv("CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR"), + DefaultUpgrade: getAddressFromEnv("CONTRACTS_DEFAULT_UPGRADE_ADDR"), + DiamondProxy: getAddressFromEnv("CONTRACTS_DIAMOND_PROXY_ADDR"), + Verifier: getAddressFromEnv("CONTRACTS_VERIFIER_ADDR"), + }, + Bridges: { + ERC20BridgeImplementation: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR"), + ERC20BridgeProxy: getAddressFromEnv("CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR"), + WethBridgeImplementation: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR"), + WethBridgeProxy: getAddressFromEnv("CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR"), + }, + AllowList: getAddressFromEnv("CONTRACTS_L1_ALLOW_LIST_ADDR"), + Create2Factory: getAddressFromEnv("CONTRACTS_CREATE2_FACTORY_ADDR"), + ValidatorTimeLock: getAddressFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_ADDR"), + Governance: getAddressFromEnv("CONTRACTS_GOVERNANCE_ADDR"), + }; } export class Deployer { - public addresses: DeployedAddresses; - private deployWallet: Wallet; - private verbose: boolean; - private ownerAddress: string; - - constructor(config: DeployerConfig) { - this.deployWallet = config.deployWallet; - this.verbose = config.verbose != null ? config.verbose : false; - this.addresses = deployedAddressesFromEnv(); - this.ownerAddress = config.ownerAddress != null ? config.ownerAddress : this.deployWallet.address; - } - - public async initialProxyDiamondCut() { - const facetCuts = Object.values( - await getCurrentFacetCutsForAdd( - this.addresses.ZkSync.AdminFacet, - this.addresses.ZkSync.GettersFacet, - this.addresses.ZkSync.MailboxFacet, - this.addresses.ZkSync.ExecutorFacet - ) - ); - const genesisBatchHash = getHashFromEnv('CONTRACTS_GENESIS_ROOT'); // TODO: confusing name - const genesisIndexRepeatedStorageChanges = getNumberFromEnv('CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX'); - const genesisBatchCommitment = getHashFromEnv('CONTRACTS_GENESIS_BATCH_COMMITMENT'); - - const verifierParams = - process.env['CONTRACTS_PROVER_AT_GENESIS'] == 'fri' - ? { - recursionNodeLevelVkHash: getHashFromEnv('CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH'), - recursionLeafLevelVkHash: getHashFromEnv('CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH'), - recursionCircuitsSetVksHash: '0x0000000000000000000000000000000000000000000000000000000000000000' - } - : { - recursionNodeLevelVkHash: getHashFromEnv('CONTRACTS_RECURSION_NODE_LEVEL_VK_HASH'), - recursionLeafLevelVkHash: getHashFromEnv('CONTRACTS_RECURSION_LEAF_LEVEL_VK_HASH'), - recursionCircuitsSetVksHash: getHashFromEnv('CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH') - }; - const priorityTxMaxGasLimit = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); - const initialProtocolVersion = getNumberFromEnv('CONTRACTS_INITIAL_PROTOCOL_VERSION'); - const DiamondInit = new Interface(hardhat.artifacts.readArtifactSync('DiamondInit').abi); - - const diamondInitCalldata = DiamondInit.encodeFunctionData('initialize', [ - { - verifier: this.addresses.ZkSync.Verifier, - governor: this.ownerAddress, - admin: this.ownerAddress, - genesisBatchHash, - genesisIndexRepeatedStorageChanges, - genesisBatchCommitment, - allowList: this.addresses.AllowList, - verifierParams, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, - l2DefaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, - priorityTxMaxGasLimit, - initialProtocolVersion - } - ]); - - // @ts-ignore - return diamondCut(facetCuts, this.addresses.ZkSync.DiamondInit, diamondInitCalldata); - } - - public async deployCreate2Factory(ethTxOptions?: ethers.providers.TransactionRequest) { - if (this.verbose) { - console.log('Deploying Create2 factory'); - } - - const contractFactory = await hardhat.ethers.getContractFactory('SingletonFactory', { - signer: this.deployWallet - }); - - const create2Factory = await contractFactory.deploy(...[ethTxOptions]); - const rec = await create2Factory.deployTransaction.wait(); - - if (this.verbose) { - console.log(`CONTRACTS_CREATE2_FACTORY_ADDR=${create2Factory.address}`); - console.log(`Create2 factory deployed, gasUsed: ${rec.gasUsed.toString()}`); - } - - this.addresses.Create2Factory = create2Factory.address; - } - - private async deployViaCreate2( - contractName: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args: any[], - create2Salt: string, - ethTxOptions: ethers.providers.TransactionRequest, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - libraries?: any - ) { - const result = await deployViaCreate2( - this.deployWallet, - contractName, - args, - create2Salt, - ethTxOptions, - this.addresses.Create2Factory, - this.verbose, - libraries - ); - return result[0]; - } - - public async deployGovernance(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2( - 'Governance', - // TODO: load parameters from config - [this.ownerAddress, ethers.constants.AddressZero, 0], - create2Salt, - ethTxOptions - ); - - if (this.verbose) { - console.log(`CONTRACTS_GOVERNANCE_ADDR=${contractAddress}`); - } - - this.addresses.Governance = contractAddress; + public addresses: DeployedAddresses; + private deployWallet: Wallet; + private verbose: boolean; + private ownerAddress: string; + + constructor(config: DeployerConfig) { + this.deployWallet = config.deployWallet; + this.verbose = config.verbose != null ? config.verbose : false; + this.addresses = deployedAddressesFromEnv(); + this.ownerAddress = config.ownerAddress != null ? config.ownerAddress : this.deployWallet.address; + } + + public async initialProxyDiamondCut() { + const facetCuts = Object.values( + await getCurrentFacetCutsForAdd( + this.addresses.ZkSync.AdminFacet, + this.addresses.ZkSync.GettersFacet, + this.addresses.ZkSync.MailboxFacet, + this.addresses.ZkSync.ExecutorFacet + ) + ); + const genesisBatchHash = getHashFromEnv("CONTRACTS_GENESIS_ROOT"); // TODO: confusing name + const genesisIndexRepeatedStorageChanges = getNumberFromEnv("CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX"); + const genesisBatchCommitment = getHashFromEnv("CONTRACTS_GENESIS_BATCH_COMMITMENT"); + + const verifierParams = + process.env["CONTRACTS_PROVER_AT_GENESIS"] == "fri" + ? { + recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH"), + recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH"), + recursionCircuitsSetVksHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + } + : { + recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_NODE_LEVEL_VK_HASH"), + recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_LEAF_LEVEL_VK_HASH"), + recursionCircuitsSetVksHash: getHashFromEnv("CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH"), + }; + const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); + const initialProtocolVersion = getNumberFromEnv("CONTRACTS_INITIAL_PROTOCOL_VERSION"); + const DiamondInit = new Interface(hardhat.artifacts.readArtifactSync("DiamondInit").abi); + + const diamondInitCalldata = DiamondInit.encodeFunctionData("initialize", [ + { + verifier: this.addresses.ZkSync.Verifier, + governor: this.ownerAddress, + admin: this.ownerAddress, + genesisBatchHash, + genesisIndexRepeatedStorageChanges, + genesisBatchCommitment, + allowList: this.addresses.AllowList, + verifierParams, + zkPorterIsAvailable: false, + l2BootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, + l2DefaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + priorityTxMaxGasLimit, + initialProtocolVersion, + }, + ]); + + // @ts-ignore + return diamondCut(facetCuts, this.addresses.ZkSync.DiamondInit, diamondInitCalldata); + } + + public async deployCreate2Factory(ethTxOptions?: ethers.providers.TransactionRequest) { + if (this.verbose) { + console.log("Deploying Create2 factory"); + } + + const contractFactory = await hardhat.ethers.getContractFactory("SingletonFactory", { + signer: this.deployWallet, + }); + + const create2Factory = await contractFactory.deploy(...[ethTxOptions]); + const rec = await create2Factory.deployTransaction.wait(); + + if (this.verbose) { + console.log(`CONTRACTS_CREATE2_FACTORY_ADDR=${create2Factory.address}`); + console.log(`Create2 factory deployed, gasUsed: ${rec.gasUsed.toString()}`); + } + + this.addresses.Create2Factory = create2Factory.address; + } + + private async deployViaCreate2( + contractName: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args: any[], + create2Salt: string, + ethTxOptions: ethers.providers.TransactionRequest, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + libraries?: any + ) { + const result = await deployViaCreate2( + this.deployWallet, + contractName, + args, + create2Salt, + ethTxOptions, + this.addresses.Create2Factory, + this.verbose, + libraries + ); + return result[0]; + } + + public async deployGovernance(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2( + "Governance", + // TODO: load parameters from config + [this.ownerAddress, ethers.constants.AddressZero, 0], + create2Salt, + ethTxOptions + ); + + if (this.verbose) { + console.log(`CONTRACTS_GOVERNANCE_ADDR=${contractAddress}`); + } + + this.addresses.Governance = contractAddress; + } + + public async deployAllowList(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("AllowList", [this.ownerAddress], create2Salt, ethTxOptions); + + if (this.verbose) { + console.log(`CONTRACTS_L1_ALLOW_LIST_ADDR=${contractAddress}`); + } + + this.addresses.AllowList = contractAddress; + } + + public async deployMailboxFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("MailboxFacet", [], create2Salt, ethTxOptions); + + if (this.verbose) { + console.log(`CONTRACTS_MAILBOX_FACET_ADDR=${contractAddress}`); + } + + this.addresses.ZkSync.MailboxFacet = contractAddress; + } + + public async deployAdminFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("AdminFacet", [], create2Salt, ethTxOptions); + + if (this.verbose) { + console.log(`CONTRACTS_ADMIN_FACET_ADDR=${contractAddress}`); } - public async deployAllowList(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2( - 'AllowList', - [this.ownerAddress], - create2Salt, - ethTxOptions - ); + this.addresses.ZkSync.AdminFacet = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_L1_ALLOW_LIST_ADDR=${contractAddress}`); - } - - this.addresses.AllowList = contractAddress; - } - - public async deployMailboxFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('MailboxFacet', [], create2Salt, ethTxOptions); - - if (this.verbose) { - console.log(`CONTRACTS_MAILBOX_FACET_ADDR=${contractAddress}`); - } - - this.addresses.ZkSync.MailboxFacet = contractAddress; - } - - public async deployAdminFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('AdminFacet', [], create2Salt, ethTxOptions); - - if (this.verbose) { - console.log(`CONTRACTS_ADMIN_FACET_ADDR=${contractAddress}`); - } - - this.addresses.ZkSync.AdminFacet = contractAddress; - } - - public async deployExecutorFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('ExecutorFacet', [], create2Salt, ethTxOptions); + public async deployExecutorFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("ExecutorFacet", [], create2Salt, ethTxOptions); if (this.verbose) { console.log(`ETH_SENDER_SENDER_VALIDIUM_MODE=false`); console.log(`CONTRACTS_EXECUTOR_FACET_ADDR=${contractAddress}`); } - this.addresses.ZkSync.ExecutorFacet = contractAddress; - } + this.addresses.ZkSync.ExecutorFacet = contractAddress; + } - public async deployValidiumExecutorFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('ValidiumExecutorFacet', [], create2Salt, ethTxOptions); + public async deployValidiumExecutorFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2('ValidiumExecutorFacet', [], create2Salt, ethTxOptions); if (this.verbose) { console.log(`ETH_SENDER_SENDER_VALIDIUM_MODE=true`); console.log(`CONTRACTS_VALIDIUM_EXECUTOR_FACET_ADDR=${contractAddress}`); } - this.addresses.ZkSync.ExecutorFacet = contractAddress; - } - - public async deployGettersFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('GettersFacet', [], create2Salt, ethTxOptions); + this.addresses.ZkSync.ExecutorFacet = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_GETTERS_FACET_ADDR=${contractAddress}`); - } + public async deployGettersFacet(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("GettersFacet", [], create2Salt, ethTxOptions); - this.addresses.ZkSync.GettersFacet = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_GETTERS_FACET_ADDR=${contractAddress}`); } - public async deployVerifier(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('Verifier', [], create2Salt, ethTxOptions); + this.addresses.ZkSync.GettersFacet = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_VERIFIER_ADDR=${contractAddress}`); - } + public async deployVerifier(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("Verifier", [], create2Salt, ethTxOptions); - this.addresses.ZkSync.Verifier = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_VERIFIER_ADDR=${contractAddress}`); } - public async deployERC20BridgeImplementation( - create2Salt: string, - ethTxOptions: ethers.providers.TransactionRequest - ) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2( - 'L1ERC20Bridge', - [this.addresses.ZkSync.DiamondProxy, this.addresses.AllowList], - create2Salt, - ethTxOptions - ); + this.addresses.ZkSync.Verifier = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR=${contractAddress}`); - } + public async deployERC20BridgeImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2( + "L1ERC20Bridge", + [this.addresses.ZkSync.DiamondProxy, this.addresses.AllowList], + create2Salt, + ethTxOptions + ); - this.addresses.Bridges.ERC20BridgeImplementation = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_L1_ERC20_BRIDGE_IMPL_ADDR=${contractAddress}`); } - public async deployERC20BridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2( - 'TransparentUpgradeableProxy', - [this.addresses.Bridges.ERC20BridgeImplementation, this.ownerAddress, '0x'], - create2Salt, - ethTxOptions - ); + this.addresses.Bridges.ERC20BridgeImplementation = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR=${contractAddress}`); - } + public async deployERC20BridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [this.addresses.Bridges.ERC20BridgeImplementation, this.ownerAddress, "0x"], + create2Salt, + ethTxOptions + ); - this.addresses.Bridges.ERC20BridgeProxy = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_L1_ERC20_BRIDGE_PROXY_ADDR=${contractAddress}`); } - public async deployWethToken(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('WETH9', [], create2Salt, ethTxOptions); + this.addresses.Bridges.ERC20BridgeProxy = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_L1_WETH_TOKEN_ADDR=${contractAddress}`); - } + public async deployWethToken(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("WETH9", [], create2Salt, ethTxOptions); + + if (this.verbose) { + console.log(`CONTRACTS_L1_WETH_TOKEN_ADDR=${contractAddress}`); } + } - public async deployWethBridgeImplementation( - create2Salt: string, - ethTxOptions: ethers.providers.TransactionRequest - ) { - const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || 'localhost'); - const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == 'WETH')!.address; - - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2( - 'L1WethBridge', - [l1WethToken, this.addresses.ZkSync.DiamondProxy, this.addresses.AllowList], - create2Salt, - ethTxOptions - ); + public async deployWethBridgeImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || "localhost"); + const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; - if (this.verbose) { - console.log(`CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR=${contractAddress}`); - } + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2( + "L1WethBridge", + [l1WethToken, this.addresses.ZkSync.DiamondProxy, this.addresses.AllowList], + create2Salt, + ethTxOptions + ); - this.addresses.Bridges.WethBridgeImplementation = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_L1_WETH_BRIDGE_IMPL_ADDR=${contractAddress}`); } - public async deployWethBridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2( - 'TransparentUpgradeableProxy', - [this.addresses.Bridges.WethBridgeImplementation, this.ownerAddress, '0x'], - create2Salt, - ethTxOptions - ); + this.addresses.Bridges.WethBridgeImplementation = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR=${contractAddress}`); - } + public async deployWethBridgeProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2( + "TransparentUpgradeableProxy", + [this.addresses.Bridges.WethBridgeImplementation, this.ownerAddress, "0x"], + create2Salt, + ethTxOptions + ); - this.addresses.Bridges.WethBridgeProxy = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_L1_WETH_BRIDGE_PROXY_ADDR=${contractAddress}`); } - public async deployDiamondInit(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('DiamondInit', [], create2Salt, ethTxOptions); + this.addresses.Bridges.WethBridgeProxy = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_DIAMOND_INIT_ADDR=${contractAddress}`); - } + public async deployDiamondInit(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("DiamondInit", [], create2Salt, ethTxOptions); - this.addresses.ZkSync.DiamondInit = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_DIAMOND_INIT_ADDR=${contractAddress}`); } - public async deployDiamondUpgradeInit( - create2Salt: string, - contractVersion: number, - ethTxOptions: ethers.providers.TransactionRequest - ) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2( - `DiamondUpgradeInit${contractVersion}`, - [], - create2Salt, - ethTxOptions - ); + this.addresses.ZkSync.DiamondInit = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR=${contractAddress}`); - } + public async deployDiamondUpgradeInit( + create2Salt: string, + contractVersion: number, + ethTxOptions: ethers.providers.TransactionRequest + ) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2( + `DiamondUpgradeInit${contractVersion}`, + [], + create2Salt, + ethTxOptions + ); - this.addresses.ZkSync.DiamondUpgradeInit = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_DIAMOND_UPGRADE_INIT_ADDR=${contractAddress}`); } - public async deployDefaultUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('DefaultUpgrade', [], create2Salt, ethTxOptions); + this.addresses.ZkSync.DiamondUpgradeInit = contractAddress; + } - if (this.verbose) { - console.log(`CONTRACTS_DEFAULT_UPGRADE_ADDR=${contractAddress}`); - } + public async deployDefaultUpgrade(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("DefaultUpgrade", [], create2Salt, ethTxOptions); - this.addresses.ZkSync.DefaultUpgrade = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_DEFAULT_UPGRADE_ADDR=${contractAddress}`); } - public async deployDiamondProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; + this.addresses.ZkSync.DefaultUpgrade = contractAddress; + } - const chainId = getNumberFromEnv('ETH_CLIENT_CHAIN_ID'); - const initialDiamondCut = await this.initialProxyDiamondCut(); - const contractAddress = await this.deployViaCreate2( - 'DiamondProxy', - [chainId, initialDiamondCut], - create2Salt, - ethTxOptions - ); + public async deployDiamondProxy(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; - if (this.verbose) { - console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${contractAddress}`); - } + const chainId = getNumberFromEnv("ETH_CLIENT_CHAIN_ID"); + const initialDiamondCut = await this.initialProxyDiamondCut(); + const contractAddress = await this.deployViaCreate2( + "DiamondProxy", + [chainId, initialDiamondCut], + create2Salt, + ethTxOptions + ); - this.addresses.ZkSync.DiamondProxy = contractAddress; + if (this.verbose) { + console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${contractAddress}`); } - public async deployZkSyncContract(create2Salt: string, gasPrice?: BigNumberish, nonce?, validiumMode?: boolean) { - nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); - - const executorFacetPromise = validiumMode - ? this.deployValidiumExecutorFacet(create2Salt, { gasPrice, nonce: nonce + 1 }) - : this.deployExecutorFacet(create2Salt, { gasPrice, nonce: nonce + 1 }); - - // deploy zkSync contract - const independentZkSyncDeployPromises = [ - this.deployMailboxFacet(create2Salt, { gasPrice, nonce }), - executorFacetPromise, - this.deployAdminFacet(create2Salt, { gasPrice, nonce: nonce + 2 }), - this.deployGettersFacet(create2Salt, { gasPrice, nonce: nonce + 3 }), - this.deployDiamondInit(create2Salt, { gasPrice, nonce: nonce + 4 }) - ]; - await Promise.all(independentZkSyncDeployPromises); - nonce += 5; - - await this.deployDiamondProxy(create2Salt, { gasPrice, nonce }); - } + this.addresses.ZkSync.DiamondProxy = contractAddress; + } - public async deployBridgeContracts(create2Salt: string, gasPrice?: BigNumberish, nonce?) { - nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); + public async deployZkSyncContract(create2Salt: string, gasPrice?: BigNumberish, nonce?, validiumMode?: boolean) { + nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); - await this.deployERC20BridgeImplementation(create2Salt, { gasPrice, nonce: nonce }); - await this.deployERC20BridgeProxy(create2Salt, { gasPrice, nonce: nonce + 1 }); - } + const executorFacetPromise = validiumMode + ? this.deployValidiumExecutorFacet(create2Salt, { gasPrice, nonce: nonce + 1 }) + : this.deployExecutorFacet(create2Salt, { gasPrice, nonce: nonce + 1 }); - public async deployWethBridgeContracts(create2Salt: string, gasPrice?: BigNumberish, nonce?) { - nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); + // deploy zkSync contract + const independentZkSyncDeployPromises = [ + this.deployMailboxFacet(create2Salt, { gasPrice, nonce }), + executorFacetPromise, + this.deployAdminFacet(create2Salt, { gasPrice, nonce: nonce + 2 }), + this.deployGettersFacet(create2Salt, { gasPrice, nonce: nonce + 3 }), + this.deployDiamondInit(create2Salt, { gasPrice, nonce: nonce + 4 }), + ]; + await Promise.all(independentZkSyncDeployPromises); + nonce += 5; - await this.deployWethBridgeImplementation(create2Salt, { gasPrice, nonce: nonce++ }); - await this.deployWethBridgeProxy(create2Salt, { gasPrice, nonce: nonce++ }); - } + await this.deployDiamondProxy(create2Salt, { gasPrice, nonce }); + } - public async deployValidatorTimelock(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const executionDelay = getNumberFromEnv('CONTRACTS_VALIDATOR_TIMELOCK_EXECUTION_DELAY'); - const validatorAddress = getAddressFromEnv('ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR'); - const contractAddress = await this.deployViaCreate2( - 'ValidatorTimelock', - [this.ownerAddress, this.addresses.ZkSync.DiamondProxy, executionDelay, validatorAddress], - create2Salt, - ethTxOptions - ); + public async deployBridgeContracts(create2Salt: string, gasPrice?: BigNumberish, nonce?) { + nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); - if (this.verbose) { - console.log(`CONTRACTS_VALIDATOR_TIMELOCK_ADDR=${contractAddress}`); - } + await this.deployERC20BridgeImplementation(create2Salt, { gasPrice, nonce: nonce }); + await this.deployERC20BridgeProxy(create2Salt, { gasPrice, nonce: nonce + 1 }); + } - this.addresses.ValidatorTimeLock = contractAddress; - } + public async deployWethBridgeContracts(create2Salt: string, gasPrice?: BigNumberish, nonce?) { + nonce = nonce ? parseInt(nonce) : await this.deployWallet.getTransactionCount(); - public async deployMulticall3(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2('Multicall3', [], create2Salt, ethTxOptions); + await this.deployWethBridgeImplementation(create2Salt, { gasPrice, nonce: nonce++ }); + await this.deployWethBridgeProxy(create2Salt, { gasPrice, nonce: nonce++ }); + } - if (this.verbose) { - console.log(`CONTRACTS_L1_MULTICALL3_ADDR=${contractAddress}`); - } - } + public async deployValidatorTimelock(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const executionDelay = getNumberFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_EXECUTION_DELAY"); + const validatorAddress = getAddressFromEnv("ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR"); + const contractAddress = await this.deployViaCreate2( + "ValidatorTimelock", + [this.ownerAddress, this.addresses.ZkSync.DiamondProxy, executionDelay, validatorAddress], + create2Salt, + ethTxOptions + ); - public transparentUpgradableProxyContract(address, signerOrProvider: Signer | providers.Provider) { - return TransparentUpgradeableProxyFactory.connect(address, signerOrProvider); + if (this.verbose) { + console.log(`CONTRACTS_VALIDATOR_TIMELOCK_ADDR=${contractAddress}`); } - public create2FactoryContract(signerOrProvider: Signer | providers.Provider) { - return SingletonFactoryFactory.connect(this.addresses.Create2Factory, signerOrProvider); - } + this.addresses.ValidatorTimeLock = contractAddress; + } - public governanceContract(signerOrProvider: Signer | providers.Provider) { - return IGovernanceFactory.connect(this.addresses.Governance, signerOrProvider); - } + public async deployMulticall3(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { + ethTxOptions.gasLimit ??= 10_000_000; + const contractAddress = await this.deployViaCreate2("Multicall3", [], create2Salt, ethTxOptions); - public zkSyncContract(signerOrProvider: Signer | providers.Provider) { - return IZkSyncFactory.connect(this.addresses.ZkSync.DiamondProxy, signerOrProvider); + if (this.verbose) { + console.log(`CONTRACTS_L1_MULTICALL3_ADDR=${contractAddress}`); } + } - public validatorTimelock(signerOrProvider: Signer | providers.Provider) { - return ValidatorTimelockFactory.connect(this.addresses.ValidatorTimeLock, signerOrProvider); - } + public transparentUpgradableProxyContract(address, signerOrProvider: Signer | providers.Provider) { + return TransparentUpgradeableProxyFactory.connect(address, signerOrProvider); + } - public l1AllowList(signerOrProvider: Signer | providers.Provider) { - return AllowListFactory.connect(this.addresses.AllowList, signerOrProvider); - } + public create2FactoryContract(signerOrProvider: Signer | providers.Provider) { + return SingletonFactoryFactory.connect(this.addresses.Create2Factory, signerOrProvider); + } - public defaultERC20Bridge(signerOrProvider: Signer | providers.Provider) { - return L1ERC20BridgeFactory.connect(this.addresses.Bridges.ERC20BridgeProxy, signerOrProvider); - } + public governanceContract(signerOrProvider: Signer | providers.Provider) { + return IGovernanceFactory.connect(this.addresses.Governance, signerOrProvider); + } - public defaultWethBridge(signerOrProvider: Signer | providers.Provider) { - return L1WethBridgeFactory.connect(this.addresses.Bridges.WethBridgeProxy, signerOrProvider); - } + public zkSyncContract(signerOrProvider: Signer | providers.Provider) { + return IZkSyncFactory.connect(this.addresses.ZkSync.DiamondProxy, signerOrProvider); + } + + public validatorTimelock(signerOrProvider: Signer | providers.Provider) { + return ValidatorTimelockFactory.connect(this.addresses.ValidatorTimeLock, signerOrProvider); + } + + public l1AllowList(signerOrProvider: Signer | providers.Provider) { + return AllowListFactory.connect(this.addresses.AllowList, signerOrProvider); + } + + public defaultERC20Bridge(signerOrProvider: Signer | providers.Provider) { + return L1ERC20BridgeFactory.connect(this.addresses.Bridges.ERC20BridgeProxy, signerOrProvider); + } + + public defaultWethBridge(signerOrProvider: Signer | providers.Provider) { + return L1WethBridgeFactory.connect(this.addresses.Bridges.WethBridgeProxy, signerOrProvider); + } } diff --git a/l1-contracts/src.ts/diamondCut.ts b/l1-contracts/src.ts/diamondCut.ts index caae7f409..564a3e53f 100644 --- a/l1-contracts/src.ts/diamondCut.ts +++ b/l1-contracts/src.ts/diamondCut.ts @@ -1,121 +1,121 @@ -import * as hardhat from 'hardhat'; -import type { Interface } from 'ethers/lib/utils'; -import '@nomiclabs/hardhat-ethers'; -import type { Wallet } from 'ethers'; -import { ethers } from 'ethers'; -import { IZkSyncFactory } from '../typechain/IZkSyncFactory'; -import { IBaseFactory } from '../typechain/IBaseFactory'; +import * as hardhat from "hardhat"; +import type { Interface } from "ethers/lib/utils"; +import "@nomiclabs/hardhat-ethers"; +import type { Wallet } from "ethers"; +import { ethers } from "ethers"; +import { IZkSyncFactory } from "../typechain/IZkSyncFactory"; +import { IBaseFactory } from "../typechain/IBaseFactory"; // Some of the facets are to be removed with the upcoming upgrade. -const UNCONDITIONALLY_REMOVED_FACETS = ['DiamondCutFacet', 'GovernanceFacet']; +const UNCONDITIONALLY_REMOVED_FACETS = ["DiamondCutFacet", "GovernanceFacet"]; export enum Action { - Add = 0, - Replace = 1, - Remove = 2 + Add = 0, + Replace = 1, + Remove = 2, } export interface FacetCut { - facet: string; - selectors: string[]; - action: Action; - isFreezable: boolean; + facet: string; + selectors: string[]; + action: Action; + isFreezable: boolean; } export interface DiamondCut { - facetCuts: FacetCut[]; - initAddress: string; - initCalldata: string; + facetCuts: FacetCut[]; + initAddress: string; + initCalldata: string; } export function facetCut(address: string, contract: Interface, action: Action, isFreezable: boolean): FacetCut { - return { - facet: address, - selectors: getAllSelectors(contract), - action, - isFreezable - }; + return { + facet: address, + selectors: getAllSelectors(contract), + action, + isFreezable, + }; } export function diamondCut(facetCuts: FacetCut[], initAddress: string, initCalldata: string): DiamondCut { - return { - facetCuts, - initAddress, - initCalldata - }; + return { + facetCuts, + initAddress, + initCalldata, + }; } export function getAllSelectors(contractInterface: Interface) { - return Object.keys(contractInterface.functions) - .filter((signature) => { - return signature !== 'getName()'; - }) - .map((signature) => contractInterface.getSighash(signature)); + return Object.keys(contractInterface.functions) + .filter((signature) => { + return signature !== "getName()"; + }) + .map((signature) => contractInterface.getSighash(signature)); } export async function getCurrentFacetCutsForAdd( - adminAddress: string, - gettersAddress: string, - mailboxAddress: string, - executorAddress: string + adminAddress: string, + gettersAddress: string, + mailboxAddress: string, + executorAddress: string ) { - const facetsCuts = {}; - // Some facets should always be available regardless of freezing: upgradability system, getters, etc. - // And for some facets there are should be possibility to freeze them by the governor if we found a bug inside. - if (adminAddress) { - // Should be unfreezable. The function to unfreeze contract is located on the admin facet. - // That means if the admin facet will be freezable, the proxy can NEVER be unfrozen. - const adminFacet = await hardhat.ethers.getContractAt('AdminFacet', adminAddress); - facetsCuts['AdminFacet'] = facetCut(adminFacet.address, adminFacet.interface, Action.Add, false); - } - if (gettersAddress) { - // Should be unfreezable. There are getters, that users can expect to be available. - const getters = await hardhat.ethers.getContractAt('GettersFacet', gettersAddress); - facetsCuts['GettersFacet'] = facetCut(getters.address, getters.interface, Action.Add, false); - } - // These contracts implement the logic without which we can get out of the freeze. - if (mailboxAddress) { - const mailbox = await hardhat.ethers.getContractAt('MailboxFacet', mailboxAddress); - facetsCuts['MailboxFacet'] = facetCut(mailbox.address, mailbox.interface, Action.Add, true); - } - if (executorAddress) { - const executor = await hardhat.ethers.getContractAt('ExecutorFacet', executorAddress); - facetsCuts['ExecutorFacet'] = facetCut(executor.address, executor.interface, Action.Add, true); - } + const facetsCuts = {}; + // Some facets should always be available regardless of freezing: upgradability system, getters, etc. + // And for some facets there are should be possibility to freeze them by the governor if we found a bug inside. + if (adminAddress) { + // Should be unfreezable. The function to unfreeze contract is located on the admin facet. + // That means if the admin facet will be freezable, the proxy can NEVER be unfrozen. + const adminFacet = await hardhat.ethers.getContractAt("AdminFacet", adminAddress); + facetsCuts["AdminFacet"] = facetCut(adminFacet.address, adminFacet.interface, Action.Add, false); + } + if (gettersAddress) { + // Should be unfreezable. There are getters, that users can expect to be available. + const getters = await hardhat.ethers.getContractAt("GettersFacet", gettersAddress); + facetsCuts["GettersFacet"] = facetCut(getters.address, getters.interface, Action.Add, false); + } + // These contracts implement the logic without which we can get out of the freeze. + if (mailboxAddress) { + const mailbox = await hardhat.ethers.getContractAt("MailboxFacet", mailboxAddress); + facetsCuts["MailboxFacet"] = facetCut(mailbox.address, mailbox.interface, Action.Add, true); + } + if (executorAddress) { + const executor = await hardhat.ethers.getContractAt("ExecutorFacet", executorAddress); + facetsCuts["ExecutorFacet"] = facetCut(executor.address, executor.interface, Action.Add, true); + } - return facetsCuts; + return facetsCuts; } export async function getDeployedFacetCutsForRemove(wallet: Wallet, zkSyncAddress: string, updatedFaceNames: string[]) { - const mainContract = IZkSyncFactory.connect(zkSyncAddress, wallet); - const diamondCutFacets = await mainContract.facets(); - // We don't care about freezing, because we are removing the facets. - const result = []; - for (const { addr, selectors } of diamondCutFacets) { - const facet = IBaseFactory.connect(addr, wallet); - const facetName = await facet.getName(); - if (updatedFaceNames.includes(facetName)) { - result.push({ - facet: ethers.constants.AddressZero, - selectors, - action: Action.Remove, - isFreezable: false - }); - } + const mainContract = IZkSyncFactory.connect(zkSyncAddress, wallet); + const diamondCutFacets = await mainContract.facets(); + // We don't care about freezing, because we are removing the facets. + const result = []; + for (const { addr, selectors } of diamondCutFacets) { + const facet = IBaseFactory.connect(addr, wallet); + const facetName = await facet.getName(); + if (updatedFaceNames.includes(facetName)) { + result.push({ + facet: ethers.constants.AddressZero, + selectors, + action: Action.Remove, + isFreezable: false, + }); } - return result; + } + return result; } export async function getFacetCutsForUpgrade( - wallet: Wallet, - zkSyncAddress: string, - adminAddress: string, - gettersAddress: string, - mailboxAddress: string, - executorAddress: string + wallet: Wallet, + zkSyncAddress: string, + adminAddress: string, + gettersAddress: string, + mailboxAddress: string, + executorAddress: string ) { - const newFacetCuts = await getCurrentFacetCutsForAdd(adminAddress, gettersAddress, mailboxAddress, executorAddress); - const namesOfFacetsToBeRemoved = [...UNCONDITIONALLY_REMOVED_FACETS, ...Object.keys(newFacetCuts)]; - const oldFacetCuts = await getDeployedFacetCutsForRemove(wallet, zkSyncAddress, namesOfFacetsToBeRemoved); - return [...oldFacetCuts, ...Object.values(newFacetCuts)]; + const newFacetCuts = await getCurrentFacetCutsForAdd(adminAddress, gettersAddress, mailboxAddress, executorAddress); + const namesOfFacetsToBeRemoved = [...UNCONDITIONALLY_REMOVED_FACETS, ...Object.keys(newFacetCuts)]; + const oldFacetCuts = await getDeployedFacetCutsForRemove(wallet, zkSyncAddress, namesOfFacetsToBeRemoved); + return [...oldFacetCuts, ...Object.values(newFacetCuts)]; } diff --git a/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts b/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts index 523dffdae..fbd2997ff 100644 --- a/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts +++ b/l1-contracts/test/unit_tests/erc20-bridge-upgrade.fork.ts @@ -1,92 +1,89 @@ -import { expect } from 'chai'; -import * as hardhat from 'hardhat'; +import { expect } from "chai"; +import * as hardhat from "hardhat"; -import type { ethers } from 'ethers'; -import type { L1ERC20Bridge } from '../../typechain'; -import { L1ERC20BridgeTestFactory } from '../../typechain'; +import type { ethers } from "ethers"; +import type { L1ERC20Bridge } from "../../typechain"; +import { L1ERC20BridgeTestFactory } from "../../typechain"; -import type { ITransparentUpgradeableProxy } from '../../typechain/ITransparentUpgradeableProxy'; -import { ITransparentUpgradeableProxyFactory } from '../../typechain/ITransparentUpgradeableProxyFactory'; +import type { ITransparentUpgradeableProxy } from "../../typechain/ITransparentUpgradeableProxy"; +import { ITransparentUpgradeableProxyFactory } from "../../typechain/ITransparentUpgradeableProxyFactory"; // TODO: change to the mainet config -const L1_ERC20_BRIDGE = '0x927DdFcc55164a59E0F33918D13a2D559bC10ce7'; -const GOVERNOR_ADDRESS = '0x98591957D9741e7E7d58FC253044e0A014A3a323'; +const L1_ERC20_BRIDGE = "0x927DdFcc55164a59E0F33918D13a2D559bC10ce7"; +const GOVERNOR_ADDRESS = "0x98591957D9741e7E7d58FC253044e0A014A3a323"; // eslint-disable-next-line @typescript-eslint/no-explicit-any async function isPromiseFailed(promise: Promise): Promise { - let failed = false; - try { - await promise; - } catch { - failed = true; - } - return failed; + let failed = false; + try { + await promise; + } catch { + failed = true; + } + return failed; } -describe('L1 ERC20 proxy upgrade fork test', function () { - const allowListOnNewImplementation = '0xdeadbeafdeadbeafdeadbeafdeadbeafdeadbeaf'; - const mailboxOnNewImplementation = '0x1234567890123456789012345678901234567890'; +describe("L1 ERC20 proxy upgrade fork test", function () { + const allowListOnNewImplementation = "0xdeadbeafdeadbeafdeadbeafdeadbeafdeadbeaf"; + const mailboxOnNewImplementation = "0x1234567890123456789012345678901234567890"; - let governor: ethers.Signer; - let randomSigner: ethers.Signer; - let bridgeProxy: ITransparentUpgradeableProxy; - let newBridgeImplementation: L1ERC20Bridge; - let oldBridgeImplementationAddress: string; + let governor: ethers.Signer; + let randomSigner: ethers.Signer; + let bridgeProxy: ITransparentUpgradeableProxy; + let newBridgeImplementation: L1ERC20Bridge; + let oldBridgeImplementationAddress: string; - before(async () => { - await hardhat.network.provider.request({ method: 'hardhat_impersonateAccount', params: [GOVERNOR_ADDRESS] }); - governor = await hardhat.ethers.provider.getSigner(GOVERNOR_ADDRESS); - await hardhat.network.provider.send('hardhat_setBalance', [GOVERNOR_ADDRESS, '0xfffffffffffffffff']); + before(async () => { + await hardhat.network.provider.request({ method: "hardhat_impersonateAccount", params: [GOVERNOR_ADDRESS] }); + governor = await hardhat.ethers.provider.getSigner(GOVERNOR_ADDRESS); + await hardhat.network.provider.send("hardhat_setBalance", [GOVERNOR_ADDRESS, "0xfffffffffffffffff"]); - const signers = await hardhat.ethers.getSigners(); - randomSigner = signers[0]; - bridgeProxy = ITransparentUpgradeableProxyFactory.connect(L1_ERC20_BRIDGE, randomSigner); - oldBridgeImplementationAddress = await bridgeProxy.connect(governor).callStatic.implementation(); + const signers = await hardhat.ethers.getSigners(); + randomSigner = signers[0]; + bridgeProxy = ITransparentUpgradeableProxyFactory.connect(L1_ERC20_BRIDGE, randomSigner); + oldBridgeImplementationAddress = await bridgeProxy.connect(governor).callStatic.implementation(); - const l1Erc20BridgeFactory = await hardhat.ethers.getContractFactory('L1ERC20BridgeTest'); - const l1Erc20Bridge = await l1Erc20BridgeFactory.deploy( - mailboxOnNewImplementation, - allowListOnNewImplementation - ); - newBridgeImplementation = L1ERC20BridgeTestFactory.connect(l1Erc20Bridge.address, l1Erc20Bridge.signer); - }); + const l1Erc20BridgeFactory = await hardhat.ethers.getContractFactory("L1ERC20BridgeTest"); + const l1Erc20Bridge = await l1Erc20BridgeFactory.deploy(mailboxOnNewImplementation, allowListOnNewImplementation); + newBridgeImplementation = L1ERC20BridgeTestFactory.connect(l1Erc20Bridge.address, l1Erc20Bridge.signer); + }); - it('should revert on non-existed methods', async () => { - const bridgeProxyAsNewImplementation = L1ERC20BridgeTestFactory.connect(bridgeProxy.address, randomSigner); + it("should revert on non-existed methods", async () => { + const bridgeProxyAsNewImplementation = L1ERC20BridgeTestFactory.connect(bridgeProxy.address, randomSigner); - const failedGetAllowList = await isPromiseFailed(bridgeProxyAsNewImplementation.getAllowList()); - expect(failedGetAllowList).to.be.true; + const failedGetAllowList = await isPromiseFailed(bridgeProxyAsNewImplementation.getAllowList()); + expect(failedGetAllowList).to.be.true; - const failedGetMailbox = await isPromiseFailed(bridgeProxyAsNewImplementation.getZkSyncMailbox()); - expect(failedGetMailbox).to.be.true; - }); + const failedGetMailbox = await isPromiseFailed(bridgeProxyAsNewImplementation.getZkSyncMailbox()); + expect(failedGetMailbox).to.be.true; + }); - it('should upgrade', async () => { - await bridgeProxy.connect(governor).upgradeTo(newBridgeImplementation.address); - }); + it("should upgrade", async () => { + await bridgeProxy.connect(governor).upgradeTo(newBridgeImplementation.address); + }); - it('check new functions', async () => { - const bridgeProxyAsNewImplementation = L1ERC20BridgeTestFactory.connect(bridgeProxy.address, randomSigner); + it("check new functions", async () => { + const bridgeProxyAsNewImplementation = L1ERC20BridgeTestFactory.connect(bridgeProxy.address, randomSigner); - const allowlist = await bridgeProxyAsNewImplementation.getAllowList(); - expect(allowlist.toLocaleLowerCase()).to.be.eq(allowListOnNewImplementation.toLocaleLowerCase()); + const allowlist = await bridgeProxyAsNewImplementation.getAllowList(); + expect(allowlist.toLocaleLowerCase()).to.be.eq(allowListOnNewImplementation.toLocaleLowerCase()); - const mailbox = await bridgeProxyAsNewImplementation.getZkSyncMailbox(); - expect(mailbox.toLocaleLowerCase()).to.be.eq(mailboxOnNewImplementation.toLocaleLowerCase()); - }); + const mailbox = await bridgeProxyAsNewImplementation.getZkSyncMailbox(); + expect(mailbox.toLocaleLowerCase()).to.be.eq(mailboxOnNewImplementation.toLocaleLowerCase()); + }); - it('should upgrade second time', async () => { - const bridgeAsTransparentProxy = ITransparentUpgradeableProxyFactory.connect(bridgeProxy.address, governor); - await bridgeAsTransparentProxy.upgradeTo(oldBridgeImplementationAddress); - }); + it("should upgrade second time", async () => { + const bridgeAsTransparentProxy = ITransparentUpgradeableProxyFactory.connect(bridgeProxy.address, governor); + await bridgeAsTransparentProxy.upgradeTo(oldBridgeImplementationAddress); + }); - it('should revert on non-existed methods', async () => { - const bridgeProxyAsNewImplementation = L1ERC20BridgeTestFactory.connect(bridgeProxy.address, randomSigner); + it("should revert on non-existed methods", async () => { + const bridgeProxyAsNewImplementation = L1ERC20BridgeTestFactory.connect(bridgeProxy.address, randomSigner); - const failedGetAllowList = await isPromiseFailed(bridgeProxyAsNewImplementation.getAllowList()); - expect(failedGetAllowList).to.be.true; + const failedGetAllowList = await isPromiseFailed(bridgeProxyAsNewImplementation.getAllowList()); + expect(failedGetAllowList).to.be.true; - const failedGetMailbox = await isPromiseFailed(bridgeProxyAsNewImplementation.getZkSyncMailbox()); - expect(failedGetMailbox).to.be.true; - }); + const failedGetMailbox = await isPromiseFailed(bridgeProxyAsNewImplementation.getZkSyncMailbox()); + expect(failedGetMailbox).to.be.true; + }); }); diff --git a/l1-contracts/test/unit_tests/executor_proof.spec.ts b/l1-contracts/test/unit_tests/executor_proof.spec.ts index 83bd60be2..20e65a78a 100644 --- a/l1-contracts/test/unit_tests/executor_proof.spec.ts +++ b/l1-contracts/test/unit_tests/executor_proof.spec.ts @@ -1,65 +1,65 @@ -import * as hardhat from 'hardhat'; -import { expect } from 'chai'; -import type { ExecutorProvingTest } from '../../typechain'; -import { ExecutorProvingTestFactory } from '../../typechain'; +import * as hardhat from "hardhat"; +import { expect } from "chai"; +import type { ExecutorProvingTest } from "../../typechain"; +import { ExecutorProvingTestFactory } from "../../typechain"; -describe('Executor test', function () { - let executor: ExecutorProvingTest; +describe("Executor test", function () { + let executor: ExecutorProvingTest; - before(async function () { - const factory = await hardhat.ethers.getContractFactory('ExecutorProvingTest'); - const executorContract = await factory.deploy(); - executor = ExecutorProvingTestFactory.connect(executorContract.address, executorContract.signer); - }); + before(async function () { + const factory = await hardhat.ethers.getContractFactory("ExecutorProvingTest"); + const executorContract = await factory.deploy(); + executor = ExecutorProvingTestFactory.connect(executorContract.address, executorContract.signer); + }); - /// This test is based on a block generated in a local system. - it('Test hashes', async () => { - const bootloaderHash = '0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a'; - const aaHash = '0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0'; - const setResult = await executor.setHashes(aaHash, bootloaderHash); - const finish = await setResult.wait(); - expect(finish.status == 1); + /// This test is based on a block generated in a local system. + it("Test hashes", async () => { + const bootloaderHash = "0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a"; + const aaHash = "0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0"; + const setResult = await executor.setHashes(aaHash, bootloaderHash); + const finish = await setResult.wait(); + expect(finish.status == 1); - const nextBatch = { - // ignored - batchNumber: 1, - // ignored - timestamp: 100, - indexRepeatedStorageChanges: 84, - newStateRoot: '0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6', - // ignored - numberOfLayer1Txs: 10, - // ignored - priorityOperationsHash: '0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183', - bootloaderHeapInitialContentsHash: '0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0', - eventsQueueStateHash: '0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea', - systemLogs: - '0x00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc', - totalL2ToL1Pubdata: - '0x0000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe' - }; + const nextBatch = { + // ignored + batchNumber: 1, + // ignored + timestamp: 100, + indexRepeatedStorageChanges: 84, + newStateRoot: "0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6", + // ignored + numberOfLayer1Txs: 10, + // ignored + priorityOperationsHash: "0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183", + bootloaderHeapInitialContentsHash: "0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0", + eventsQueueStateHash: "0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea", + systemLogs: + "0x00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc", + totalL2ToL1Pubdata: + "0x0000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe", + }; - const processL2Logs = await executor.processL2Logs( - nextBatch, - '0x0000000000000000000000000000000000000000000000000000000000000000' - ); - expect(processL2Logs.stateDiffHash, 'State diff hash computation failed').is.equal( - '0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc' - ); + const processL2Logs = await executor.processL2Logs( + nextBatch, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + expect(processL2Logs.stateDiffHash, "State diff hash computation failed").is.equal( + "0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc" + ); - const nextCommitment = await executor.createBatchCommitment(nextBatch, processL2Logs.stateDiffHash); - console.log('This block Commitment is : ' + nextCommitment); - expect(nextCommitment, 'Commitment computation failed').is.equal( - '0x5765f7967c60dcf0e77ba0a909980c19b5ceab56f6bc1a6e0bd308f5f8dec263' - ); + const nextCommitment = await executor.createBatchCommitment(nextBatch, processL2Logs.stateDiffHash); + console.log("This block Commitment is : " + nextCommitment); + expect(nextCommitment, "Commitment computation failed").is.equal( + "0x5765f7967c60dcf0e77ba0a909980c19b5ceab56f6bc1a6e0bd308f5f8dec263" + ); - const prevCommitment = '0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404'; - const result = await executor.getBatchProofPublicInput(prevCommitment, nextCommitment, { - recursionNodeLevelVkHash: '0x5a3ef282b21e12fe1f4438e5bb158fc5060b160559c5158c6389d62d9fe3d080', - recursionLeafLevelVkHash: '0x72167c43a46cf38875b267d67716edc4563861364a3c03ab7aee73498421e828', - // ignored. - recursionCircuitsSetVksHash: '0x05dc05911af0aee6a0950ee36dad423981cf05a58cfdb479109bff3c2262eaac' - }); - expect(result.toHexString(), '').to.be.equal('0xa37cc4d4684f5f0ddafc193a2ab9e364c1a8ebb2b30594c1f1e7dc08'); + const prevCommitment = "0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404"; + const result = await executor.getBatchProofPublicInput(prevCommitment, nextCommitment, { + recursionNodeLevelVkHash: "0x5a3ef282b21e12fe1f4438e5bb158fc5060b160559c5158c6389d62d9fe3d080", + recursionLeafLevelVkHash: "0x72167c43a46cf38875b267d67716edc4563861364a3c03ab7aee73498421e828", + // ignored. + recursionCircuitsSetVksHash: "0x05dc05911af0aee6a0950ee36dad423981cf05a58cfdb479109bff3c2262eaac", }); + expect(result.toHexString(), "").to.be.equal("0xa37cc4d4684f5f0ddafc193a2ab9e364c1a8ebb2b30594c1f1e7dc08"); + }); }); diff --git a/l1-contracts/test/unit_tests/governance_test.spec.ts b/l1-contracts/test/unit_tests/governance_test.spec.ts index 36d319fce..88837a0e6 100644 --- a/l1-contracts/test/unit_tests/governance_test.spec.ts +++ b/l1-contracts/test/unit_tests/governance_test.spec.ts @@ -1,109 +1,107 @@ -import { expect } from 'chai'; -import * as ethers from 'ethers'; -import * as hardhat from 'hardhat'; -import type { AdminFacetTest } from '../../typechain'; -import { AdminFacetTestFactory, GovernanceFactory } from '../../typechain'; -import { getCallRevertReason } from './utils'; +import { expect } from "chai"; +import * as ethers from "ethers"; +import * as hardhat from "hardhat"; +import type { AdminFacetTest } from "../../typechain"; +import { AdminFacetTestFactory, GovernanceFactory } from "../../typechain"; +import { getCallRevertReason } from "./utils"; function randomAddress() { - return ethers.utils.hexlify(ethers.utils.randomBytes(20)); + return ethers.utils.hexlify(ethers.utils.randomBytes(20)); } -describe('Admin facet tests', function () { - let adminFacetTest: AdminFacetTest; - let randomSigner: ethers.Signer; +describe("Admin facet tests", function () { + let adminFacetTest: AdminFacetTest; + let randomSigner: ethers.Signer; + + before(async () => { + const contractFactory = await hardhat.ethers.getContractFactory("AdminFacetTest"); + const contract = await contractFactory.deploy(); + adminFacetTest = AdminFacetTestFactory.connect(contract.address, contract.signer); + + const governanceContract = await contractFactory.deploy(); + const governance = GovernanceFactory.connect(governanceContract.address, governanceContract.signer); + await adminFacetTest.setPendingGovernor(governance.address); + + randomSigner = (await hardhat.ethers.getSigners())[1]; + }); + + it("governor successfully set validator", async () => { + const validatorAddress = randomAddress(); + await adminFacetTest.setValidator(validatorAddress, true); + + const isValidator = await adminFacetTest.isValidator(validatorAddress); + expect(isValidator).to.equal(true); + }); + + it("random account fails to set validator", async () => { + const validatorAddress = randomAddress(); + const revertReason = await getCallRevertReason( + adminFacetTest.connect(randomSigner).setValidator(validatorAddress, true) + ); + expect(revertReason).equal("Only by governor or admin"); + }); + + it("governor successfully set porter availability", async () => { + await adminFacetTest.setPorterAvailability(true); + + const porterAvailability = await adminFacetTest.getPorterAvailability(); + expect(porterAvailability).to.equal(true); + }); + + it("random account fails to set porter availability", async () => { + const revertReason = await getCallRevertReason(adminFacetTest.connect(randomSigner).setPorterAvailability(false)); + expect(revertReason).equal("1g"); + }); + + it("governor successfully set priority transaction max gas limit", async () => { + const gasLimit = "12345678"; + await adminFacetTest.setPriorityTxMaxGasLimit(gasLimit); + + const newGasLimit = await adminFacetTest.getPriorityTxMaxGasLimit(); + expect(newGasLimit).to.equal(gasLimit); + }); + + it("random account fails to priority transaction max gas limit", async () => { + const gasLimit = "123456789"; + const revertReason = await getCallRevertReason( + adminFacetTest.connect(randomSigner).setPriorityTxMaxGasLimit(gasLimit) + ); + expect(revertReason).equal("1g"); + }); + + describe("change governor", function () { + let newGovernor: ethers.Signer; before(async () => { - const contractFactory = await hardhat.ethers.getContractFactory('AdminFacetTest'); - const contract = await contractFactory.deploy(); - adminFacetTest = AdminFacetTestFactory.connect(contract.address, contract.signer); - - const governanceContract = await contractFactory.deploy(); - const governance = GovernanceFactory.connect(governanceContract.address, governanceContract.signer); - await adminFacetTest.setPendingGovernor(governance.address); - - randomSigner = (await hardhat.ethers.getSigners())[1]; - }); - - it('governor successfully set validator', async () => { - const validatorAddress = randomAddress(); - await adminFacetTest.setValidator(validatorAddress, true); - - const isValidator = await adminFacetTest.isValidator(validatorAddress); - expect(isValidator).to.equal(true); - }); - - it('random account fails to set validator', async () => { - const validatorAddress = randomAddress(); - const revertReason = await getCallRevertReason( - adminFacetTest.connect(randomSigner).setValidator(validatorAddress, true) - ); - expect(revertReason).equal('Only by governor or admin'); + newGovernor = (await hardhat.ethers.getSigners())[2]; }); - it('governor successfully set porter availability', async () => { - await adminFacetTest.setPorterAvailability(true); - - const porterAvailability = await adminFacetTest.getPorterAvailability(); - expect(porterAvailability).to.equal(true); - }); + it("set pending governor", async () => { + const proposedGovernor = await randomSigner.getAddress(); + await adminFacetTest.setPendingGovernor(proposedGovernor); - it('random account fails to set porter availability', async () => { - const revertReason = await getCallRevertReason( - adminFacetTest.connect(randomSigner).setPorterAvailability(false) - ); - expect(revertReason).equal('1g'); + const pendingGovernor = await adminFacetTest.getPendingGovernor(); + expect(pendingGovernor).equal(proposedGovernor); }); - it('governor successfully set priority transaction max gas limit', async () => { - const gasLimit = '12345678'; - await adminFacetTest.setPriorityTxMaxGasLimit(gasLimit); + it("reset pending governor", async () => { + const proposedGovernor = await newGovernor.getAddress(); + await adminFacetTest.setPendingGovernor(proposedGovernor); - const newGasLimit = await adminFacetTest.getPriorityTxMaxGasLimit(); - expect(newGasLimit).to.equal(gasLimit); + const pendingGovernor = await adminFacetTest.getPendingGovernor(); + expect(pendingGovernor).equal(proposedGovernor); }); - it('random account fails to priority transaction max gas limit', async () => { - const gasLimit = '123456789'; - const revertReason = await getCallRevertReason( - adminFacetTest.connect(randomSigner).setPriorityTxMaxGasLimit(gasLimit) - ); - expect(revertReason).equal('1g'); + it("failed to accept governor from not proposed account", async () => { + const revertReason = await getCallRevertReason(adminFacetTest.connect(randomSigner).acceptGovernor()); + expect(revertReason).equal("n4"); }); - describe('change governor', function () { - let newGovernor: ethers.Signer; - - before(async () => { - newGovernor = (await hardhat.ethers.getSigners())[2]; - }); - - it('set pending governor', async () => { - const proposedGovernor = await randomSigner.getAddress(); - await adminFacetTest.setPendingGovernor(proposedGovernor); - - const pendingGovernor = await adminFacetTest.getPendingGovernor(); - expect(pendingGovernor).equal(proposedGovernor); - }); - - it('reset pending governor', async () => { - const proposedGovernor = await newGovernor.getAddress(); - await adminFacetTest.setPendingGovernor(proposedGovernor); - - const pendingGovernor = await adminFacetTest.getPendingGovernor(); - expect(pendingGovernor).equal(proposedGovernor); - }); - - it('failed to accept governor from not proposed account', async () => { - const revertReason = await getCallRevertReason(adminFacetTest.connect(randomSigner).acceptGovernor()); - expect(revertReason).equal('n4'); - }); - - it('accept governor from proposed account', async () => { - await adminFacetTest.connect(newGovernor).acceptGovernor(); + it("accept governor from proposed account", async () => { + await adminFacetTest.connect(newGovernor).acceptGovernor(); - const governor = await adminFacetTest.getGovernor(); - expect(governor).equal(await newGovernor.getAddress()); - }); + const governor = await adminFacetTest.getGovernor(); + expect(governor).equal(await newGovernor.getAddress()); }); + }); }); diff --git a/l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts index d14126992..fb74c52d4 100644 --- a/l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts +++ b/l1-contracts/test/unit_tests/l1_erc20_bridge_test.spec.ts @@ -1,242 +1,239 @@ -import { expect } from 'chai'; -import { ethers } from 'ethers'; -import * as hardhat from 'hardhat'; -import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from 'zksync-web3/build/src/utils'; -import type { IZkSync } from 'zksync-web3/build/typechain'; -import { IZkSyncFactory } from 'zksync-web3/build/typechain'; -import { Action, diamondCut, facetCut } from '../../src.ts/diamondCut'; -import type { AllowList, TestnetERC20Token } from '../../typechain'; +import { expect } from "chai"; +import { ethers } from "ethers"; +import * as hardhat from "hardhat"; +import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from "zksync-web3/build/src/utils"; +import type { IZkSync } from "zksync-web3/build/typechain"; +import { IZkSyncFactory } from "zksync-web3/build/typechain"; +import { Action, diamondCut, facetCut } from "../../src.ts/diamondCut"; +import type { AllowList, TestnetERC20Token } from "../../typechain"; import { - AllowListFactory, - DiamondInitFactory, - GettersFacetFactory, - MailboxFacetFactory, - TestnetERC20TokenFactory -} from '../../typechain'; -import type { IL1Bridge } from '../../typechain/IL1Bridge'; -import { IL1BridgeFactory } from '../../typechain/IL1BridgeFactory'; -import { AccessMode, getCallRevertReason } from './utils'; - -describe('L1ERC20Bridge tests', function () { - let owner: ethers.Signer; - let randomSigner: ethers.Signer; - let allowList: AllowList; - let l1ERC20Bridge: IL1Bridge; - let erc20TestToken: TestnetERC20Token; - let testnetERC20TokenContract: ethers.Contract; - let l1Erc20BridgeContract: ethers.Contract; - let zksyncContract: IZkSync; - - before(async () => { - [owner, randomSigner] = await hardhat.ethers.getSigners(); - - const gettersFactory = await hardhat.ethers.getContractFactory('GettersFacet'); - const gettersContract = await gettersFactory.deploy(); - const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); - - const mailboxFactory = await hardhat.ethers.getContractFactory('MailboxFacet'); - const mailboxContract = await mailboxFactory.deploy(); - const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); - - const allowListFactory = await hardhat.ethers.getContractFactory('AllowList'); - const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); - allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); - - const diamondInitFactory = await hardhat.ethers.getContractFactory('DiamondInit'); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const diamondInitData = diamondInit.interface.encodeFunctionData('initialize', [ - { - verifier: dummyAddress, - governor: await owner.getAddress(), - admin: await owner.getAddress(), - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - allowList: allowList.address, - verifierParams: { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero - }, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0 - } - ]); - - const facetCuts = [ - facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), - facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true) - ]; - - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); - - const diamondProxyFactory = await hardhat.ethers.getContractFactory('DiamondProxy'); - const chainId = hardhat.network.config.chainId; - const diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); - - const l1Erc20BridgeFactory = await hardhat.ethers.getContractFactory('L1ERC20Bridge'); - l1Erc20BridgeContract = await l1Erc20BridgeFactory.deploy( - diamondProxyContract.address, - allowListContract.address - ); - l1ERC20Bridge = IL1BridgeFactory.connect(l1Erc20BridgeContract.address, l1Erc20BridgeContract.signer); - - const testnetERC20TokenFactory = await hardhat.ethers.getContractFactory('TestnetERC20Token'); - testnetERC20TokenContract = await testnetERC20TokenFactory.deploy('TestToken', 'TT', 18); - erc20TestToken = TestnetERC20TokenFactory.connect( - testnetERC20TokenContract.address, - testnetERC20TokenContract.signer - ); - - await erc20TestToken.mint(await randomSigner.getAddress(), ethers.utils.parseUnits('10000', 18)); - await erc20TestToken - .connect(randomSigner) - .approve(l1Erc20BridgeContract.address, ethers.utils.parseUnits('10000', 18)); - - await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); - - // Exposing the methods of IZkSync to the diamond proxy - zksyncContract = IZkSyncFactory.connect(diamondProxyContract.address, diamondProxyContract.provider); - }); - - it('Should not allow an un-whitelisted address to deposit', async () => { - const revertReason = await getCallRevertReason( - l1ERC20Bridge - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - testnetERC20TokenContract.address, - 0, - 0, - 0, - ethers.constants.AddressZero - ) - ); - expect(revertReason).equal('nr'); - - await (await allowList.setAccessMode(l1Erc20BridgeContract.address, AccessMode.Public)).wait(); - }); - - it('Should not allow depositing zero amount', async () => { - const revertReason = await getCallRevertReason( - l1ERC20Bridge - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - testnetERC20TokenContract.address, - 0, - 0, - 0, - ethers.constants.AddressZero - ) - ); - expect(revertReason).equal('2T'); - }); - - it('Should deposit successfully', async () => { - const depositorAddress = await randomSigner.getAddress(); - await depositERC20( - l1ERC20Bridge.connect(randomSigner), - zksyncContract, - depositorAddress, - testnetERC20TokenContract.address, - ethers.utils.parseUnits('800', 18), - 10000000 - ); - }); - - it('Should revert on finalizing a withdrawal with wrong message length', async () => { - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, '0x', []) - ); - expect(revertReason).equal('kk'); - }); - - it('Should revert on finalizing a withdrawal with wrong function signature', async () => { - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, ethers.utils.randomBytes(76), []) - ); - expect(revertReason).equal('nt'); - }); - - it('Should revert on finalizing a withdrawal with wrong batch number', async () => { - const functionSignature = '0x11a2ccc1'; - const l1Receiver = await randomSigner.getAddress(); - const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, - l1Receiver, - testnetERC20TokenContract.address, - ethers.constants.HashZero - ]); - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(10, 0, 0, l2ToL1message, []) - ); - expect(revertReason).equal('xx'); - }); - - it('Should revert on finalizing a withdrawal with wrong length of proof', async () => { - const functionSignature = '0x11a2ccc1'; - const l1Receiver = await randomSigner.getAddress(); - const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, - l1Receiver, - testnetERC20TokenContract.address, - ethers.constants.HashZero - ]); - const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, l2ToL1message, []) - ); - expect(revertReason).equal('xc'); - }); - - it('Should revert on finalizing a withdrawal with wrong proof', async () => { - const functionSignature = '0x11a2ccc1'; - const l1Receiver = await randomSigner.getAddress(); - const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, - l1Receiver, - testnetERC20TokenContract.address, - ethers.constants.HashZero - ]); - const revertReason = await getCallRevertReason( - l1ERC20Bridge - .connect(randomSigner) - .finalizeWithdrawal(0, 0, 0, l2ToL1message, Array(9).fill(ethers.constants.HashZero)) - ); - expect(revertReason).equal('nq'); - }); + AllowListFactory, + DiamondInitFactory, + GettersFacetFactory, + MailboxFacetFactory, + TestnetERC20TokenFactory, +} from "../../typechain"; +import type { IL1Bridge } from "../../typechain/IL1Bridge"; +import { IL1BridgeFactory } from "../../typechain/IL1BridgeFactory"; +import { AccessMode, getCallRevertReason } from "./utils"; + +describe("L1ERC20Bridge tests", function () { + let owner: ethers.Signer; + let randomSigner: ethers.Signer; + let allowList: AllowList; + let l1ERC20Bridge: IL1Bridge; + let erc20TestToken: TestnetERC20Token; + let testnetERC20TokenContract: ethers.Contract; + let l1Erc20BridgeContract: ethers.Contract; + let zksyncContract: IZkSync; + + before(async () => { + [owner, randomSigner] = await hardhat.ethers.getSigners(); + + const gettersFactory = await hardhat.ethers.getContractFactory("GettersFacet"); + const gettersContract = await gettersFactory.deploy(); + const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); + + const mailboxFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); + const mailboxContract = await mailboxFactory.deploy(); + const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); + + const allowListFactory = await hardhat.ethers.getContractFactory("AllowList"); + const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); + allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); + + const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); + const diamondInitContract = await diamondInitFactory.deploy(); + const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); + + const dummyHash = new Uint8Array(32); + dummyHash.set([1, 0, 0, 1]); + const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ + { + verifier: dummyAddress, + governor: await owner.getAddress(), + admin: await owner.getAddress(), + genesisBatchHash: ethers.constants.HashZero, + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: ethers.constants.HashZero, + allowList: allowList.address, + verifierParams: { + recursionCircuitsSetVksHash: ethers.constants.HashZero, + recursionLeafLevelVkHash: ethers.constants.HashZero, + recursionNodeLevelVkHash: ethers.constants.HashZero, + }, + zkPorterIsAvailable: false, + l2BootloaderBytecodeHash: dummyHash, + l2DefaultAccountBytecodeHash: dummyHash, + priorityTxMaxGasLimit: 10000000, + initialProtocolVersion: 0, + }, + ]); + + const facetCuts = [ + facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), + facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true), + ]; + + const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); + + const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); + const chainId = hardhat.network.config.chainId; + const diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); + + const l1Erc20BridgeFactory = await hardhat.ethers.getContractFactory("L1ERC20Bridge"); + l1Erc20BridgeContract = await l1Erc20BridgeFactory.deploy(diamondProxyContract.address, allowListContract.address); + l1ERC20Bridge = IL1BridgeFactory.connect(l1Erc20BridgeContract.address, l1Erc20BridgeContract.signer); + + const testnetERC20TokenFactory = await hardhat.ethers.getContractFactory("TestnetERC20Token"); + testnetERC20TokenContract = await testnetERC20TokenFactory.deploy("TestToken", "TT", 18); + erc20TestToken = TestnetERC20TokenFactory.connect( + testnetERC20TokenContract.address, + testnetERC20TokenContract.signer + ); + + await erc20TestToken.mint(await randomSigner.getAddress(), ethers.utils.parseUnits("10000", 18)); + await erc20TestToken + .connect(randomSigner) + .approve(l1Erc20BridgeContract.address, ethers.utils.parseUnits("10000", 18)); + + await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); + + // Exposing the methods of IZkSync to the diamond proxy + zksyncContract = IZkSyncFactory.connect(diamondProxyContract.address, diamondProxyContract.provider); + }); + + it("Should not allow an un-whitelisted address to deposit", async () => { + const revertReason = await getCallRevertReason( + l1ERC20Bridge + .connect(randomSigner) + .deposit( + await randomSigner.getAddress(), + testnetERC20TokenContract.address, + 0, + 0, + 0, + ethers.constants.AddressZero + ) + ); + expect(revertReason).equal("nr"); + + await (await allowList.setAccessMode(l1Erc20BridgeContract.address, AccessMode.Public)).wait(); + }); + + it("Should not allow depositing zero amount", async () => { + const revertReason = await getCallRevertReason( + l1ERC20Bridge + .connect(randomSigner) + .deposit( + await randomSigner.getAddress(), + testnetERC20TokenContract.address, + 0, + 0, + 0, + ethers.constants.AddressZero + ) + ); + expect(revertReason).equal("2T"); + }); + + it("Should deposit successfully", async () => { + const depositorAddress = await randomSigner.getAddress(); + await depositERC20( + l1ERC20Bridge.connect(randomSigner), + zksyncContract, + depositorAddress, + testnetERC20TokenContract.address, + ethers.utils.parseUnits("800", 18), + 10000000 + ); + }); + + it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, "0x", []) + ); + expect(revertReason).equal("kk"); + }); + + it("Should revert on finalizing a withdrawal with wrong function signature", async () => { + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, ethers.utils.randomBytes(76), []) + ); + expect(revertReason).equal("nt"); + }); + + it("Should revert on finalizing a withdrawal with wrong batch number", async () => { + const functionSignature = "0x11a2ccc1"; + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + testnetERC20TokenContract.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(10, 0, 0, l2ToL1message, []) + ); + expect(revertReason).equal("xx"); + }); + + it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { + const functionSignature = "0x11a2ccc1"; + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + testnetERC20TokenContract.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(0, 0, 0, l2ToL1message, []) + ); + expect(revertReason).equal("xc"); + }); + + it("Should revert on finalizing a withdrawal with wrong proof", async () => { + const functionSignature = "0x11a2ccc1"; + const l1Receiver = await randomSigner.getAddress(); + const l2ToL1message = ethers.utils.hexConcat([ + functionSignature, + l1Receiver, + testnetERC20TokenContract.address, + ethers.constants.HashZero, + ]); + const revertReason = await getCallRevertReason( + l1ERC20Bridge + .connect(randomSigner) + .finalizeWithdrawal(0, 0, 0, l2ToL1message, Array(9).fill(ethers.constants.HashZero)) + ); + expect(revertReason).equal("nq"); + }); }); async function depositERC20( - bridge: IL1Bridge, - zksyncContract: IZkSync, - l2Receiver: string, - l1Token: string, - amount: ethers.BigNumber, - l2GasLimit: number, - l2RefundRecipient = ethers.constants.AddressZero + bridge: IL1Bridge, + zksyncContract: IZkSync, + l2Receiver: string, + l1Token: string, + amount: ethers.BigNumber, + l2GasLimit: number, + l2RefundRecipient = ethers.constants.AddressZero ) { - const gasPrice = await bridge.provider.getGasPrice(); - const gasPerPubdata = REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; - const neededValue = await zksyncContract.l2TransactionBaseCost(gasPrice, l2GasLimit, gasPerPubdata); - - await bridge.deposit( - l2Receiver, - l1Token, - amount, - l2GasLimit, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, - l2RefundRecipient, - { - value: neededValue - } - ); + const gasPrice = await bridge.provider.getGasPrice(); + const gasPerPubdata = REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + const neededValue = await zksyncContract.l2TransactionBaseCost(gasPrice, l2GasLimit, gasPerPubdata); + + await bridge.deposit( + l2Receiver, + l1Token, + amount, + l2GasLimit, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + l2RefundRecipient, + { + value: neededValue, + } + ); } diff --git a/l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts index b09f3b68b..5d09caf4e 100644 --- a/l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts +++ b/l1-contracts/test/unit_tests/l1_weth_bridge_test.spec.ts @@ -1,248 +1,232 @@ -import { expect } from 'chai'; -import { ethers } from 'ethers'; -import * as hardhat from 'hardhat'; -import { hashL2Bytecode } from '../../scripts/utils'; -import { Action, diamondCut, facetCut } from '../../src.ts/diamondCut'; -import type { AllowList, L1WethBridge, WETH9 } from '../../typechain'; +import { expect } from "chai"; +import { ethers } from "ethers"; +import * as hardhat from "hardhat"; +import { hashL2Bytecode } from "../../scripts/utils"; +import { Action, diamondCut, facetCut } from "../../src.ts/diamondCut"; +import type { AllowList, L1WethBridge, WETH9 } from "../../typechain"; import { - AllowListFactory, - DiamondInitFactory, - GettersFacetFactory, - L1WethBridgeFactory, - MailboxFacetFactory, - WETH9Factory -} from '../../typechain'; -import type { IZkSync } from '../../typechain/IZkSync'; -import { AccessMode, getCallRevertReason } from './utils'; - -import { Interface } from 'ethers/lib/utils'; -import type { Address } from 'zksync-web3/build/src/types'; - -const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008006'; + AllowListFactory, + DiamondInitFactory, + GettersFacetFactory, + L1WethBridgeFactory, + MailboxFacetFactory, + WETH9Factory, +} from "../../typechain"; +import type { IZkSync } from "../../typechain/IZkSync"; +import { AccessMode, getCallRevertReason } from "./utils"; + +import { Interface } from "ethers/lib/utils"; +import type { Address } from "zksync-web3/build/src/types"; + +const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; // eslint-disable-next-line @typescript-eslint/no-var-requires -const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require('../../../SystemConfig.json').REQUIRED_L2_GAS_PRICE_PER_PUBDATA; +const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../../SystemConfig.json").REQUIRED_L2_GAS_PRICE_PER_PUBDATA; export async function create2DeployFromL1( - zkSync: IZkSync, - walletAddress: Address, - bytecode: ethers.BytesLike, - constructor: ethers.BytesLike, - create2Salt: ethers.BytesLike, - l2GasLimit: ethers.BigNumberish + zkSync: IZkSync, + walletAddress: Address, + bytecode: ethers.BytesLike, + constructor: ethers.BytesLike, + create2Salt: ethers.BytesLike, + l2GasLimit: ethers.BigNumberish ) { - const deployerSystemContracts = new Interface(hardhat.artifacts.readArtifactSync('IContractDeployer').abi); - const bytecodeHash = hashL2Bytecode(bytecode); - const calldata = deployerSystemContracts.encodeFunctionData('create2', [create2Salt, bytecodeHash, constructor]); - const gasPrice = await zkSync.provider.getGasPrice(); - const expectedCost = await zkSync.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); - - await zkSync.requestL2Transaction( - DEPLOYER_SYSTEM_CONTRACT_ADDRESS, - 0, - calldata, - l2GasLimit, + const deployerSystemContracts = new Interface(hardhat.artifacts.readArtifactSync("IContractDeployer").abi); + const bytecodeHash = hashL2Bytecode(bytecode); + const calldata = deployerSystemContracts.encodeFunctionData("create2", [create2Salt, bytecodeHash, constructor]); + const gasPrice = await zkSync.provider.getGasPrice(); + const expectedCost = await zkSync.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); + + await zkSync.requestL2Transaction( + DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + 0, + calldata, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + [bytecode], + walletAddress, + { value: expectedCost, gasPrice } + ); +} + +describe("WETH Bridge tests", () => { + let owner: ethers.Signer; + let randomSigner: ethers.Signer; + let allowList: AllowList; + let bridgeProxy: L1WethBridge; + let l1Weth: WETH9; + const functionSignature = "0x6c0960f9"; + + before(async () => { + [owner, randomSigner] = await hardhat.ethers.getSigners(); + + // prepare the diamond + + const gettersFactory = await hardhat.ethers.getContractFactory("GettersFacet"); + const gettersContract = await gettersFactory.deploy(); + const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); + + const mailboxFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); + const mailboxContract = await mailboxFactory.deploy(); + const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); + + const allowListFactory = await hardhat.ethers.getContractFactory("AllowList"); + const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); + allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); + + const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); + const diamondInitContract = await diamondInitFactory.deploy(); + const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); + + const dummyHash = new Uint8Array(32); + dummyHash.set([1, 0, 0, 1]); + const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ + { + verifier: dummyAddress, + governor: await owner.getAddress(), + admin: await owner.getAddress(), + genesisBatchHash: ethers.constants.HashZero, + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: ethers.constants.HashZero, + allowList: allowList.address, + verifierParams: { + recursionCircuitsSetVksHash: ethers.constants.HashZero, + recursionLeafLevelVkHash: ethers.constants.HashZero, + recursionNodeLevelVkHash: ethers.constants.HashZero, + }, + zkPorterIsAvailable: false, + l2BootloaderBytecodeHash: dummyHash, + l2DefaultAccountBytecodeHash: dummyHash, + priorityTxMaxGasLimit: 10000000, + initialProtocolVersion: 0, + }, + ]); + + const facetCuts = [ + facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), + facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true), + ]; + + const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); + + const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); + const chainId = hardhat.network.config.chainId; + const diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); + + await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); + + l1Weth = WETH9Factory.connect((await (await hardhat.ethers.getContractFactory("WETH9")).deploy()).address, owner); + + // prepare the bridge + + const bridge = await ( + await hardhat.ethers.getContractFactory("L1WethBridge") + ).deploy(l1Weth.address, diamondProxyContract.address, allowListContract.address); + + // we don't test L2, so it is ok to give garbage factory deps and L2 address + const garbageBytecode = "0x1111111111111111111111111111111111111111111111111111111111111111"; + const garbageAddress = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"; + + const bridgeInitData = bridge.interface.encodeFunctionData("initialize", [ + [garbageBytecode, garbageBytecode], + garbageAddress, + await owner.getAddress(), + ethers.constants.WeiPerEther, + ethers.constants.WeiPerEther, + ]); + const _bridgeProxy = await ( + await hardhat.ethers.getContractFactory("ERC1967Proxy") + ).deploy(bridge.address, bridgeInitData, { value: ethers.constants.WeiPerEther.mul(2) }); + + bridgeProxy = L1WethBridgeFactory.connect(_bridgeProxy.address, _bridgeProxy.signer); + }); + + it("Should not allow an un-whitelisted address to deposit", async () => { + const revertReason = await getCallRevertReason( + bridgeProxy + .connect(randomSigner) + .deposit(await randomSigner.getAddress(), ethers.constants.AddressZero, 0, 0, 0, ethers.constants.AddressZero) + ); + + expect(revertReason).equal("nr"); + + // This is only so the following tests don't need whitelisting + await (await allowList.setAccessMode(bridgeProxy.address, AccessMode.Public)).wait(); + }); + + it("Should not allow depositing zero WETH", async () => { + const revertReason = await getCallRevertReason( + bridgeProxy + .connect(randomSigner) + .deposit( + await randomSigner.getAddress(), + await bridgeProxy.l1WethAddress(), + 0, + 0, + 0, + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("Amount cannot be zero"); + }); + + it("Should deposit successfully", async () => { + await l1Weth.connect(randomSigner).deposit({ value: 100 }); + await (await l1Weth.connect(randomSigner).approve(bridgeProxy.address, 100)).wait(); + await bridgeProxy + .connect(randomSigner) + .deposit( + await randomSigner.getAddress(), + l1Weth.address, + 100, + 1000000, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [bytecode], - walletAddress, - { value: expectedCost, gasPrice } + await randomSigner.getAddress(), + { value: ethers.constants.WeiPerEther } + ); + }); + + it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const revertReason = await getCallRevertReason( + bridgeProxy.connect(randomSigner).finalizeWithdrawal(0, 0, 0, "0x", []) ); -} + expect(revertReason).equal("Incorrect ETH message with additional data length"); + }); -describe('WETH Bridge tests', () => { - let owner: ethers.Signer; - let randomSigner: ethers.Signer; - let allowList: AllowList; - let bridgeProxy: L1WethBridge; - let l1Weth: WETH9; - const functionSignature = '0x6c0960f9'; - - before(async () => { - [owner, randomSigner] = await hardhat.ethers.getSigners(); - - // prepare the diamond - - const gettersFactory = await hardhat.ethers.getContractFactory('GettersFacet'); - const gettersContract = await gettersFactory.deploy(); - const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); - - const mailboxFactory = await hardhat.ethers.getContractFactory('MailboxFacet'); - const mailboxContract = await mailboxFactory.deploy(); - const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); - - const allowListFactory = await hardhat.ethers.getContractFactory('AllowList'); - const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); - allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); - - const diamondInitFactory = await hardhat.ethers.getContractFactory('DiamondInit'); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const diamondInitData = diamondInit.interface.encodeFunctionData('initialize', [ - { - verifier: dummyAddress, - governor: await owner.getAddress(), - admin: await owner.getAddress(), - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - allowList: allowList.address, - verifierParams: { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero - }, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0 - } - ]); - - const facetCuts = [ - facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), - facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true) - ]; - - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); - - const diamondProxyFactory = await hardhat.ethers.getContractFactory('DiamondProxy'); - const chainId = hardhat.network.config.chainId; - const diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); - - await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); - - l1Weth = WETH9Factory.connect( - (await (await hardhat.ethers.getContractFactory('WETH9')).deploy()).address, - owner - ); - - // prepare the bridge - - const bridge = await ( - await hardhat.ethers.getContractFactory('L1WethBridge') - ).deploy(l1Weth.address, diamondProxyContract.address, allowListContract.address); - - // we don't test L2, so it is ok to give garbage factory deps and L2 address - const garbageBytecode = '0x1111111111111111111111111111111111111111111111111111111111111111'; - const garbageAddress = '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'; - - const bridgeInitData = bridge.interface.encodeFunctionData('initialize', [ - [garbageBytecode, garbageBytecode], - garbageAddress, - await owner.getAddress(), - ethers.constants.WeiPerEther, - ethers.constants.WeiPerEther - ]); - const _bridgeProxy = await ( - await hardhat.ethers.getContractFactory('ERC1967Proxy') - ).deploy(bridge.address, bridgeInitData, { value: ethers.constants.WeiPerEther.mul(2) }); - - bridgeProxy = L1WethBridgeFactory.connect(_bridgeProxy.address, _bridgeProxy.signer); - }); - - it('Should not allow an un-whitelisted address to deposit', async () => { - const revertReason = await getCallRevertReason( - bridgeProxy - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - ethers.constants.AddressZero, - 0, - 0, - 0, - ethers.constants.AddressZero - ) - ); - - expect(revertReason).equal('nr'); - - // This is only so the following tests don't need whitelisting - await (await allowList.setAccessMode(bridgeProxy.address, AccessMode.Public)).wait(); - }); - - it('Should not allow depositing zero WETH', async () => { - const revertReason = await getCallRevertReason( - bridgeProxy - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - await bridgeProxy.l1WethAddress(), - 0, - 0, - 0, - ethers.constants.AddressZero - ) - ); - - expect(revertReason).equal('Amount cannot be zero'); - }); - - it('Should deposit successfully', async () => { - await l1Weth.connect(randomSigner).deposit({ value: 100 }); - await (await l1Weth.connect(randomSigner).approve(bridgeProxy.address, 100)).wait(); - await bridgeProxy - .connect(randomSigner) - .deposit( - await randomSigner.getAddress(), - l1Weth.address, - 100, - 1000000, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - await randomSigner.getAddress(), - { value: ethers.constants.WeiPerEther } - ); - }); - - it('Should revert on finalizing a withdrawal with wrong message length', async () => { - const revertReason = await getCallRevertReason( - bridgeProxy.connect(randomSigner).finalizeWithdrawal(0, 0, 0, '0x', []) - ); - expect(revertReason).equal('Incorrect ETH message with additional data length'); - }); - - it('Should revert on finalizing a withdrawal with wrong function selector', async () => { - const revertReason = await getCallRevertReason( - bridgeProxy.connect(randomSigner).finalizeWithdrawal(0, 0, 0, ethers.utils.randomBytes(96), []) - ); - expect(revertReason).equal('Incorrect ETH message function selector'); - }); - - it('Should revert on finalizing a withdrawal with wrong receiver', async () => { - const revertReason = await getCallRevertReason( - bridgeProxy - .connect(randomSigner) - .finalizeWithdrawal( - 0, - 0, - 0, - ethers.utils.hexConcat([functionSignature, ethers.utils.randomBytes(92)]), - [] - ) - ); - expect(revertReason).equal('Wrong L1 ETH withdraw receiver'); - }); - - it('Should revert on finalizing a withdrawal with wrong L2 sender', async () => { - const revertReason = await getCallRevertReason( - bridgeProxy - .connect(randomSigner) - .finalizeWithdrawal( - 0, - 0, - 0, - ethers.utils.hexConcat([ - functionSignature, - bridgeProxy.address, - ethers.utils.randomBytes(32), - ethers.utils.randomBytes(40) - ]), - [] - ) - ); - expect(revertReason).equal('The withdrawal was not initiated by L2 bridge'); - }); + it("Should revert on finalizing a withdrawal with wrong function selector", async () => { + const revertReason = await getCallRevertReason( + bridgeProxy.connect(randomSigner).finalizeWithdrawal(0, 0, 0, ethers.utils.randomBytes(96), []) + ); + expect(revertReason).equal("Incorrect ETH message function selector"); + }); + + it("Should revert on finalizing a withdrawal with wrong receiver", async () => { + const revertReason = await getCallRevertReason( + bridgeProxy + .connect(randomSigner) + .finalizeWithdrawal(0, 0, 0, ethers.utils.hexConcat([functionSignature, ethers.utils.randomBytes(92)]), []) + ); + expect(revertReason).equal("Wrong L1 ETH withdraw receiver"); + }); + + it("Should revert on finalizing a withdrawal with wrong L2 sender", async () => { + const revertReason = await getCallRevertReason( + bridgeProxy + .connect(randomSigner) + .finalizeWithdrawal( + 0, + 0, + 0, + ethers.utils.hexConcat([ + functionSignature, + bridgeProxy.address, + ethers.utils.randomBytes(32), + ethers.utils.randomBytes(40), + ]), + [] + ) + ); + expect(revertReason).equal("The withdrawal was not initiated by L2 bridge"); + }); }); diff --git a/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts b/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts index d129b2b77..49341c9bf 100644 --- a/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts +++ b/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts @@ -1,955 +1,949 @@ -import { expect } from 'chai'; -import * as hardhat from 'hardhat'; -import { Action, facetCut, diamondCut } from '../../src.ts/diamondCut'; -import type { AllowList, ExecutorFacet, GettersFacet, AdminFacet } from '../../typechain'; +import { expect } from "chai"; +import * as hardhat from "hardhat"; +import { Action, facetCut, diamondCut } from "../../src.ts/diamondCut"; +import type { AllowList, ExecutorFacet, GettersFacet, AdminFacet } from "../../typechain"; import { - DiamondInitFactory, - AllowListFactory, - ExecutorFacetFactory, - GettersFacetFactory, - AdminFacetFactory, - DefaultUpgradeFactory, - CustomUpgradeTestFactory -} from '../../typechain'; -import type { StoredBatchInfo, CommitBatchInfo } from './utils'; + DiamondInitFactory, + AllowListFactory, + ExecutorFacetFactory, + GettersFacetFactory, + AdminFacetFactory, + DefaultUpgradeFactory, + CustomUpgradeTestFactory, +} from "../../typechain"; +import type { StoredBatchInfo, CommitBatchInfo } from "./utils"; import { - getCallRevertReason, - AccessMode, - EMPTY_STRING_KECCAK, - genesisStoredBatchInfo, - L2_SYSTEM_CONTEXT_ADDRESS, - L2_BOOTLOADER_ADDRESS, - createSystemLogs, - SYSTEM_LOG_KEYS, - constructL2Log, - packBatchTimestampAndBatchTimestamp -} from './utils'; -import * as ethers from 'ethers'; -import type { BigNumberish, BytesLike } from 'ethers'; -import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, hashBytecode } from 'zksync-web3/build/src/utils'; + getCallRevertReason, + AccessMode, + EMPTY_STRING_KECCAK, + genesisStoredBatchInfo, + L2_SYSTEM_CONTEXT_ADDRESS, + L2_BOOTLOADER_ADDRESS, + createSystemLogs, + SYSTEM_LOG_KEYS, + constructL2Log, + packBatchTimestampAndBatchTimestamp, +} from "./utils"; +import * as ethers from "ethers"; +import type { BigNumberish, BytesLike } from "ethers"; +import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, hashBytecode } from "zksync-web3/build/src/utils"; const SYSTEM_UPGRADE_TX_TYPE = 254; -describe('L2 upgrade test', function () { - let proxyExecutor: ExecutorFacet; - let proxyAdmin: AdminFacet; - let proxyGetters: GettersFacet; - - let allowList: AllowList; - let diamondProxyContract: ethers.Contract; - let owner: ethers.Signer; - - let batch1Info: CommitBatchInfo; - let storedBatch1Info: StoredBatchInfo; - - let verifier: string; - let verifierParams: VerifierParams; - const noopUpgradeTransaction = buildL2CanonicalTransaction({ txType: 0 }); - - before(async () => { - [owner] = await hardhat.ethers.getSigners(); - - const executorFactory = await hardhat.ethers.getContractFactory('ExecutorFacet'); - const executorContract = await executorFactory.deploy(); - const executorFacet = ExecutorFacetFactory.connect(executorContract.address, executorContract.signer); - - const gettersFactory = await hardhat.ethers.getContractFactory('GettersFacet'); - const gettersContract = await gettersFactory.deploy(); - const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); - - const adminFacetFactory = await hardhat.ethers.getContractFactory('AdminFacet'); - const adminFacetContract = await adminFacetFactory.deploy(); - const adminFacet = AdminFacetFactory.connect(adminFacetContract.address, adminFacetContract.signer); - - const allowListFactory = await hardhat.ethers.getContractFactory('AllowList'); - const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); - allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); - - // Note, that while this testsuit is focused on testing MailboxFaucet only, - // we still need to initialize its storage via DiamondProxy - const diamondInitFactory = await hardhat.ethers.getContractFactory('DiamondInit'); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - verifier = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - verifierParams = { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero - }; - const diamondInitData = diamondInit.interface.encodeFunctionData('initialize', [ - { - verifier, - governor: await owner.getAddress(), - admin: await owner.getAddress(), - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - allowList: allowList.address, - verifierParams, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0 - } - ]); - - const facetCuts = [ - // Should be unfreezable. The function to unfreeze contract is located on the admin facet. - // That means if the admin will be freezable, the proxy can NEVER be unfrozen. - facetCut(adminFacet.address, adminFacet.interface, Action.Add, false), - // Should be unfreezable. There are getters, that users can expect to be available. - facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), - facetCut(executorFacet.address, executorFacet.interface, Action.Add, true) - ]; - - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); - - const diamondProxyFactory = await hardhat.ethers.getContractFactory('DiamondProxy'); - const chainId = hardhat.network.config.chainId; - diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); - - await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); - - proxyExecutor = ExecutorFacetFactory.connect(diamondProxyContract.address, owner); - proxyGetters = GettersFacetFactory.connect(diamondProxyContract.address, owner); - proxyAdmin = AdminFacetFactory.connect(diamondProxyContract.address, owner); - - await (await proxyAdmin.setValidator(await owner.getAddress(), true)).wait(); +describe("L2 upgrade test", function () { + let proxyExecutor: ExecutorFacet; + let proxyAdmin: AdminFacet; + let proxyGetters: GettersFacet; + + let allowList: AllowList; + let diamondProxyContract: ethers.Contract; + let owner: ethers.Signer; + + let batch1Info: CommitBatchInfo; + let storedBatch1Info: StoredBatchInfo; + + let verifier: string; + let verifierParams: VerifierParams; + const noopUpgradeTransaction = buildL2CanonicalTransaction({ txType: 0 }); + + before(async () => { + [owner] = await hardhat.ethers.getSigners(); + + const executorFactory = await hardhat.ethers.getContractFactory("ExecutorFacet"); + const executorContract = await executorFactory.deploy(); + const executorFacet = ExecutorFacetFactory.connect(executorContract.address, executorContract.signer); + + const gettersFactory = await hardhat.ethers.getContractFactory("GettersFacet"); + const gettersContract = await gettersFactory.deploy(); + const gettersFacet = GettersFacetFactory.connect(gettersContract.address, gettersContract.signer); + + const adminFacetFactory = await hardhat.ethers.getContractFactory("AdminFacet"); + const adminFacetContract = await adminFacetFactory.deploy(); + const adminFacet = AdminFacetFactory.connect(adminFacetContract.address, adminFacetContract.signer); + + const allowListFactory = await hardhat.ethers.getContractFactory("AllowList"); + const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); + allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); + + // Note, that while this testsuit is focused on testing MailboxFaucet only, + // we still need to initialize its storage via DiamondProxy + const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); + const diamondInitContract = await diamondInitFactory.deploy(); + const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); + + const dummyHash = new Uint8Array(32); + dummyHash.set([1, 0, 0, 1]); + verifier = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + verifierParams = { + recursionCircuitsSetVksHash: ethers.constants.HashZero, + recursionLeafLevelVkHash: ethers.constants.HashZero, + recursionNodeLevelVkHash: ethers.constants.HashZero, + }; + const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ + { + verifier, + governor: await owner.getAddress(), + admin: await owner.getAddress(), + genesisBatchHash: ethers.constants.HashZero, + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: ethers.constants.HashZero, + allowList: allowList.address, + verifierParams, + zkPorterIsAvailable: false, + l2BootloaderBytecodeHash: dummyHash, + l2DefaultAccountBytecodeHash: dummyHash, + priorityTxMaxGasLimit: 10000000, + initialProtocolVersion: 0, + }, + ]); + + const facetCuts = [ + // Should be unfreezable. The function to unfreeze contract is located on the admin facet. + // That means if the admin will be freezable, the proxy can NEVER be unfrozen. + facetCut(adminFacet.address, adminFacet.interface, Action.Add, false), + // Should be unfreezable. There are getters, that users can expect to be available. + facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), + facetCut(executorFacet.address, executorFacet.interface, Action.Add, true), + ]; + + const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); + + const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); + const chainId = hardhat.network.config.chainId; + diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); + + await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); + + proxyExecutor = ExecutorFacetFactory.connect(diamondProxyContract.address, owner); + proxyGetters = GettersFacetFactory.connect(diamondProxyContract.address, owner); + proxyAdmin = AdminFacetFactory.connect(diamondProxyContract.address, owner); + + await (await proxyAdmin.setValidator(await owner.getAddress(), true)).wait(); + }); + + it("Upgrade should work even if not all batches are processed", async () => { + batch1Info = await buildCommitBatchInfo(genesisStoredBatchInfo(), { + batchNumber: 1, }); - it('Upgrade should work even if not all batches are processed', async () => { - batch1Info = await buildCommitBatchInfo(genesisStoredBatchInfo(), { - batchNumber: 1 - }); + const commitReceipt = await (await proxyExecutor.commitBatches(genesisStoredBatchInfo(), [batch1Info])).wait(); + const commitment = commitReceipt.events[0].args.commitment; - const commitReceipt = await (await proxyExecutor.commitBatches(genesisStoredBatchInfo(), [batch1Info])).wait(); - const commitment = commitReceipt.events[0].args.commitment; + expect(await proxyGetters.getProtocolVersion()).to.equal(0); + expect(await proxyGetters.getL2SystemContractsUpgradeTxHash()).to.equal(ethers.constants.HashZero); - expect(await proxyGetters.getProtocolVersion()).to.equal(0); - expect(await proxyGetters.getL2SystemContractsUpgradeTxHash()).to.equal(ethers.constants.HashZero); + await ( + await executeUpgrade(proxyGetters, proxyAdmin, { + newProtocolVersion: 1, + l2ProtocolUpgradeTx: noopUpgradeTransaction, + }) + ).wait(); - await ( - await executeUpgrade(proxyGetters, proxyAdmin, { - newProtocolVersion: 1, - l2ProtocolUpgradeTx: noopUpgradeTransaction - }) - ).wait(); + expect(await proxyGetters.getProtocolVersion()).to.equal(1); - expect(await proxyGetters.getProtocolVersion()).to.equal(1); + storedBatch1Info = getBatchStoredInfo(batch1Info, commitment); - storedBatch1Info = getBatchStoredInfo(batch1Info, commitment); + await makeExecutedEqualCommitted(proxyExecutor, genesisStoredBatchInfo(), [storedBatch1Info], []); + }); - await makeExecutedEqualCommitted(proxyExecutor, genesisStoredBatchInfo(), [storedBatch1Info], []); + it("Timestamp should behave correctly", async () => { + // Upgrade was scheduled for now should work fine + const timeNow = (await hardhat.ethers.provider.getBlock("latest")).timestamp; + await executeUpgrade(proxyGetters, proxyAdmin, { + upgradeTimestamp: ethers.BigNumber.from(timeNow), + l2ProtocolUpgradeTx: noopUpgradeTransaction, }); - it('Timestamp should behave correctly', async () => { - // Upgrade was scheduled for now should work fine - const timeNow = (await hardhat.ethers.provider.getBlock('latest')).timestamp; - await executeUpgrade(proxyGetters, proxyAdmin, { - upgradeTimestamp: ethers.BigNumber.from(timeNow), - l2ProtocolUpgradeTx: noopUpgradeTransaction - }); - - // Upgrade that was scheduled for the future should not work now - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - upgradeTimestamp: ethers.BigNumber.from(timeNow).mul(2), - l2ProtocolUpgradeTx: noopUpgradeTransaction - }) - ); - expect(revertReason).to.equal('Upgrade is not ready yet'); + // Upgrade that was scheduled for the future should not work now + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + upgradeTimestamp: ethers.BigNumber.from(timeNow).mul(2), + l2ProtocolUpgradeTx: noopUpgradeTransaction, + }) + ); + expect(revertReason).to.equal("Upgrade is not ready yet"); + }); + + it("Should require correct tx type for upgrade tx", async () => { + const wrongTx = buildL2CanonicalTransaction({ + txType: 255, }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + }) + ); - it('Should require correct tx type for upgrade tx', async () => { - const wrongTx = buildL2CanonicalTransaction({ - txType: 255 - }); - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx - }) - ); - - expect(revertReason).to.equal('L2 system upgrade tx type is wrong'); + expect(revertReason).to.equal("L2 system upgrade tx type is wrong"); + }); + + it("Should include the new protocol version as part of nonce", async () => { + const wrongTx = buildL2CanonicalTransaction({ + txType: 254, + nonce: 0, }); - it('Should include the new protocol version as part of nonce', async () => { - const wrongTx = buildL2CanonicalTransaction({ - txType: 254, - nonce: 0 - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + newProtocolVersion: 3, + }) + ); - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 - }) - ); + expect(revertReason).to.equal("The new protocol version should be included in the L2 system upgrade tx"); + }); - expect(revertReason).to.equal('The new protocol version should be included in the L2 system upgrade tx'); + it("Should ensure monotonic protocol version", async () => { + const wrongTx = buildL2CanonicalTransaction({ + txType: 254, + nonce: 0, }); - it('Should ensure monotonic protocol version', async () => { - const wrongTx = buildL2CanonicalTransaction({ - txType: 254, - nonce: 0 - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + newProtocolVersion: 0, + }) + ); - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 0 - }) - ); + expect(revertReason).to.equal("New protocol version is not greater than the current one"); + }); - expect(revertReason).to.equal('New protocol version is not greater than the current one'); + it("Should ensure protocol version not increasing too much", async () => { + const wrongTx = buildL2CanonicalTransaction({ + txType: 254, + nonce: 0, }); - it('Should ensure protocol version not increasing too much', async () => { - const wrongTx = buildL2CanonicalTransaction({ - txType: 254, - nonce: 0 - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + newProtocolVersion: 100000, + }) + ); - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 100000 - }) - ); + expect(revertReason).to.equal("Too big protocol version difference"); + }); - expect(revertReason).to.equal('Too big protocol version difference'); + it("Should validate upgrade transaction overhead", async () => { + const wrongTx = buildL2CanonicalTransaction({ + nonce: 0, + gasLimit: 0, }); - it('Should validate upgrade transaction overhead', async () => { - const wrongTx = buildL2CanonicalTransaction({ - nonce: 0, - gasLimit: 0 - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + newProtocolVersion: 3, + }) + ); - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 - }) - ); + expect(revertReason).to.equal("my"); + }); - expect(revertReason).to.equal('my'); + it("Should validate upgrade transaction gas max", async () => { + const wrongTx = buildL2CanonicalTransaction({ + nonce: 0, + gasLimit: 1000000000000, }); - it('Should validate upgrade transaction gas max', async () => { - const wrongTx = buildL2CanonicalTransaction({ - nonce: 0, - gasLimit: 1000000000000 - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + newProtocolVersion: 3, + }) + ); - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 - }) - ); + expect(revertReason).to.equal("ui"); + }); - expect(revertReason).to.equal('ui'); + it("Should validate upgrade transaction cant output more pubdata than processable", async () => { + const wrongTx = buildL2CanonicalTransaction({ + nonce: 0, + gasLimit: 10000000, + gasPerPubdataByteLimit: 1, }); - it('Should validate upgrade transaction cant output more pubdata than processable', async () => { - const wrongTx = buildL2CanonicalTransaction({ - nonce: 0, - gasLimit: 10000000, - gasPerPubdataByteLimit: 1 - }); - - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 - }) - ); - - expect(revertReason).to.equal('uk'); - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + newProtocolVersion: 3, + }) + ); - it('Should validate factory deps', async () => { - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const wrongFactoryDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - const wrongTx = buildL2CanonicalTransaction({ - factoryDeps: [wrongFactoryDepHash], - nonce: 3 - }); - - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - factoryDeps: [myFactoryDep], - newProtocolVersion: 3 - }) - ); - - expect(revertReason).to.equal('Wrong factory dep hash'); - }); + expect(revertReason).to.equal("uk"); + }); - it('Should validate factory deps length match', async () => { - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const wrongTx = buildL2CanonicalTransaction({ - factoryDeps: [], - nonce: 3 - }); - - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - factoryDeps: [myFactoryDep], - newProtocolVersion: 3 - }) - ); - - expect(revertReason).to.equal('Wrong number of factory deps'); + it("Should validate factory deps", async () => { + const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); + const wrongFactoryDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); + const wrongTx = buildL2CanonicalTransaction({ + factoryDeps: [wrongFactoryDepHash], + nonce: 3, }); - it('Should validate factory deps length isnt too large', async () => { - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const randomDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - - const wrongTx = buildL2CanonicalTransaction({ - factoryDeps: Array(33).fill(randomDepHash), - nonce: 3 - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + factoryDeps: [myFactoryDep], + newProtocolVersion: 3, + }) + ); - const revertReason = await getCallRevertReason( - executeUpgrade(proxyGetters, proxyAdmin, { - l2ProtocolUpgradeTx: wrongTx, - factoryDeps: Array(33).fill(myFactoryDep), - newProtocolVersion: 3 - }) - ); + expect(revertReason).to.equal("Wrong factory dep hash"); + }); - expect(revertReason).to.equal('Factory deps can be at most 32'); + it("Should validate factory deps length match", async () => { + const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); + const wrongTx = buildL2CanonicalTransaction({ + factoryDeps: [], + nonce: 3, }); - let l2UpgradeTxHash: string; - it('Should successfully perform an upgrade', async () => { - const bootloaderHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - const defaultAccountHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - const newVerifier = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const newerVerifierParams = buildVerifierParams({ - recursionNodeLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), - recursionLeafLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), - recursionCircuitsSetVksHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)) - }); - - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const myFactoryDepHash = hashBytecode(myFactoryDep); - const upgradeTx = buildL2CanonicalTransaction({ - factoryDeps: [myFactoryDepHash], - nonce: 4 - }); - - const upgrade = { - bootloaderHash, - defaultAccountHash, - verifier: newVerifier, - verifierParams: newerVerifierParams, - executeUpgradeTx: true, - l2ProtocolUpgradeTx: upgradeTx, - factoryDeps: [myFactoryDep], - newProtocolVersion: 4 - }; + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + factoryDeps: [myFactoryDep], + newProtocolVersion: 3, + }) + ); - const upgradeReceipt = await (await executeUpgrade(proxyGetters, proxyAdmin, upgrade)).wait(); - - const defaultUpgradeFactory = await hardhat.ethers.getContractFactory('DefaultUpgrade'); - const upgradeEvents = upgradeReceipt.logs.map((log) => { - // Not all events can be parsed there, but we don't care about them - try { - const event = defaultUpgradeFactory.interface.parseLog(log); - const parsedArgs = event.args; - return { - name: event.name, - args: parsedArgs - }; - } catch (_) { - // @ts-ignore - } - }); - l2UpgradeTxHash = upgradeEvents.find((event) => event.name == 'UpgradeComplete').args.l2UpgradeTxHash; - - // Now, we check that all the data was set as expected - expect(await proxyGetters.getL2BootloaderBytecodeHash()).to.equal(bootloaderHash); - expect(await proxyGetters.getL2DefaultAccountBytecodeHash()).to.equal(defaultAccountHash); - expect((await proxyGetters.getVerifier()).toLowerCase()).to.equal(newVerifier.toLowerCase()); - expect(await proxyGetters.getProtocolVersion()).to.equal(4); - - const newVerifierParams = await proxyGetters.getVerifierParams(); - expect(newVerifierParams.recursionNodeLevelVkHash).to.equal(newerVerifierParams.recursionNodeLevelVkHash); - expect(newVerifierParams.recursionLeafLevelVkHash).to.equal(newerVerifierParams.recursionLeafLevelVkHash); - expect(newVerifierParams.recursionCircuitsSetVksHash).to.equal(newerVerifierParams.recursionCircuitsSetVksHash); - - expect(upgradeEvents[0].name).to.eq('NewProtocolVersion'); - expect(upgradeEvents[0].args.previousProtocolVersion.toString()).to.eq('2'); - expect(upgradeEvents[0].args.newProtocolVersion.toString()).to.eq('4'); - - expect(upgradeEvents[1].name).to.eq('NewVerifier'); - expect(upgradeEvents[1].args.oldVerifier.toLowerCase()).to.eq(verifier.toLowerCase()); - expect(upgradeEvents[1].args.newVerifier.toLowerCase()).to.eq(newVerifier.toLowerCase()); - - expect(upgradeEvents[2].name).to.eq('NewVerifierParams'); - expect(upgradeEvents[2].args.oldVerifierParams[0]).to.eq(ethers.constants.HashZero); - expect(upgradeEvents[2].args.oldVerifierParams[1]).to.eq(ethers.constants.HashZero); - expect(upgradeEvents[2].args.oldVerifierParams[2]).to.eq(ethers.constants.HashZero); - expect(upgradeEvents[2].args.newVerifierParams[0]).to.eq(newerVerifierParams.recursionNodeLevelVkHash); - expect(upgradeEvents[2].args.newVerifierParams[1]).to.eq(newerVerifierParams.recursionLeafLevelVkHash); - expect(upgradeEvents[2].args.newVerifierParams[2]).to.eq(newerVerifierParams.recursionCircuitsSetVksHash); - - expect(upgradeEvents[3].name).to.eq('NewL2BootloaderBytecodeHash'); - expect(upgradeEvents[3].args.previousBytecodeHash).to.eq( - '0x0100000100000000000000000000000000000000000000000000000000000000' - ); - expect(upgradeEvents[3].args.newBytecodeHash).to.eq(bootloaderHash); - - expect(upgradeEvents[4].name).to.eq('NewL2DefaultAccountBytecodeHash'); - expect(upgradeEvents[4].args.previousBytecodeHash).to.eq( - '0x0100000100000000000000000000000000000000000000000000000000000000' - ); - expect(upgradeEvents[4].args.newBytecodeHash).to.eq(defaultAccountHash); - }); + expect(revertReason).to.equal("Wrong number of factory deps"); + }); - it('Should fail to upgrade when there is already a pending upgrade', async () => { - const bootloaderHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - const defaultAccountHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - const verifier = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const verifierParams = buildVerifierParams({ - recursionNodeLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), - recursionLeafLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), - recursionCircuitsSetVksHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)) - }); - - const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - const myFactoryDepHash = hashBytecode(myFactoryDep); - const upgradeTx = buildL2CanonicalTransaction({ - factoryDeps: [myFactoryDepHash], - nonce: 4 - }); - - const upgrade = { - bootloaderHash, - defaultAccountHash, - verifier: verifier, - verifierParams, - executeUpgradeTx: true, - l2ProtocolUpgradeTx: upgradeTx, - factoryDeps: [myFactoryDep], - newProtocolVersion: 5 - }; - const revertReason = await getCallRevertReason(executeUpgrade(proxyGetters, proxyAdmin, upgrade)); + it("Should validate factory deps length isnt too large", async () => { + const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); + const randomDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); - expect(revertReason).to.equal('Previous upgrade has not been finalized'); + const wrongTx = buildL2CanonicalTransaction({ + factoryDeps: Array(33).fill(randomDepHash), + nonce: 3, }); - it('Should require that the next commit batches contains an upgrade tx', async () => { - if (!l2UpgradeTxHash) { - throw new Error('Can not perform this test without l2UpgradeTxHash'); - } - - const batch2InfoNoUpgradeTx = await buildCommitBatchInfo(storedBatch1Info, { - batchNumber: 2 - }); - const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoNoUpgradeTx]) - ); - expect(revertReason).to.equal('b8'); - }); + const revertReason = await getCallRevertReason( + executeUpgrade(proxyGetters, proxyAdmin, { + l2ProtocolUpgradeTx: wrongTx, + factoryDeps: Array(33).fill(myFactoryDep), + newProtocolVersion: 3, + }) + ); - it('Should ensure any additional upgrade logs go to the priority ops hash', async () => { - if (!l2UpgradeTxHash) { - throw new Error('Can not perform this test without l2UpgradeTxHash'); - } - - const systemLogs = createSystemLogs(); - systemLogs.push( - constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, - l2UpgradeTxHash - ) - ); - systemLogs.push( - constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, - l2UpgradeTxHash - ) - ); - systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) - ); - - const batch2InfoNoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, - { - batchNumber: 2 - }, - systemLogs - ); - const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoNoUpgradeTx]) - ); - expect(revertReason).to.equal('kp'); + expect(revertReason).to.equal("Factory deps can be at most 32"); + }); + + let l2UpgradeTxHash: string; + it("Should successfully perform an upgrade", async () => { + const bootloaderHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); + const defaultAccountHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); + const newVerifier = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + const newerVerifierParams = buildVerifierParams({ + recursionNodeLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + recursionLeafLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + recursionCircuitsSetVksHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), }); - it('Should fail to commit when upgrade tx hash does not match', async () => { - const timestamp = (await hardhat.ethers.provider.getBlock('latest')).timestamp; - const systemLogs = createSystemLogs(); - systemLogs.push( - constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, - ethers.constants.HashZero - ) - ); - systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) - ); - - const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, - { - batchNumber: 2, - timestamp - }, - systemLogs - ); - - const revertReason = await getCallRevertReason( - proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx]) - ); - expect(revertReason).to.equal('ut'); + const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); + const myFactoryDepHash = hashBytecode(myFactoryDep); + const upgradeTx = buildL2CanonicalTransaction({ + factoryDeps: [myFactoryDepHash], + nonce: 4, }); - it('Should commit successfully when the upgrade tx is present', async () => { - const timestamp = (await hardhat.ethers.provider.getBlock('latest')).timestamp; - const systemLogs = createSystemLogs(); - systemLogs.push( - constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, - l2UpgradeTxHash - ) - ); - systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) - ); - - const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, - { - batchNumber: 2, - timestamp - }, - systemLogs - ); - - await (await proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx])).wait(); - - expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(2); + const upgrade = { + bootloaderHash, + defaultAccountHash, + verifier: newVerifier, + verifierParams: newerVerifierParams, + executeUpgradeTx: true, + l2ProtocolUpgradeTx: upgradeTx, + factoryDeps: [myFactoryDep], + newProtocolVersion: 4, + }; + + const upgradeReceipt = await (await executeUpgrade(proxyGetters, proxyAdmin, upgrade)).wait(); + + const defaultUpgradeFactory = await hardhat.ethers.getContractFactory("DefaultUpgrade"); + const upgradeEvents = upgradeReceipt.logs.map((log) => { + // Not all events can be parsed there, but we don't care about them + try { + const event = defaultUpgradeFactory.interface.parseLog(log); + const parsedArgs = event.args; + return { + name: event.name, + args: parsedArgs, + }; + } catch (_) { + // @ts-ignore + } }); + l2UpgradeTxHash = upgradeEvents.find((event) => event.name == "UpgradeComplete").args.l2UpgradeTxHash; + + // Now, we check that all the data was set as expected + expect(await proxyGetters.getL2BootloaderBytecodeHash()).to.equal(bootloaderHash); + expect(await proxyGetters.getL2DefaultAccountBytecodeHash()).to.equal(defaultAccountHash); + expect((await proxyGetters.getVerifier()).toLowerCase()).to.equal(newVerifier.toLowerCase()); + expect(await proxyGetters.getProtocolVersion()).to.equal(4); + + const newVerifierParams = await proxyGetters.getVerifierParams(); + expect(newVerifierParams.recursionNodeLevelVkHash).to.equal(newerVerifierParams.recursionNodeLevelVkHash); + expect(newVerifierParams.recursionLeafLevelVkHash).to.equal(newerVerifierParams.recursionLeafLevelVkHash); + expect(newVerifierParams.recursionCircuitsSetVksHash).to.equal(newerVerifierParams.recursionCircuitsSetVksHash); + + expect(upgradeEvents[0].name).to.eq("NewProtocolVersion"); + expect(upgradeEvents[0].args.previousProtocolVersion.toString()).to.eq("2"); + expect(upgradeEvents[0].args.newProtocolVersion.toString()).to.eq("4"); + + expect(upgradeEvents[1].name).to.eq("NewVerifier"); + expect(upgradeEvents[1].args.oldVerifier.toLowerCase()).to.eq(verifier.toLowerCase()); + expect(upgradeEvents[1].args.newVerifier.toLowerCase()).to.eq(newVerifier.toLowerCase()); + + expect(upgradeEvents[2].name).to.eq("NewVerifierParams"); + expect(upgradeEvents[2].args.oldVerifierParams[0]).to.eq(ethers.constants.HashZero); + expect(upgradeEvents[2].args.oldVerifierParams[1]).to.eq(ethers.constants.HashZero); + expect(upgradeEvents[2].args.oldVerifierParams[2]).to.eq(ethers.constants.HashZero); + expect(upgradeEvents[2].args.newVerifierParams[0]).to.eq(newerVerifierParams.recursionNodeLevelVkHash); + expect(upgradeEvents[2].args.newVerifierParams[1]).to.eq(newerVerifierParams.recursionLeafLevelVkHash); + expect(upgradeEvents[2].args.newVerifierParams[2]).to.eq(newerVerifierParams.recursionCircuitsSetVksHash); + + expect(upgradeEvents[3].name).to.eq("NewL2BootloaderBytecodeHash"); + expect(upgradeEvents[3].args.previousBytecodeHash).to.eq( + "0x0100000100000000000000000000000000000000000000000000000000000000" + ); + expect(upgradeEvents[3].args.newBytecodeHash).to.eq(bootloaderHash); - it('Should commit successfully when batch was reverted and reupgraded', async () => { - await (await proxyExecutor.revertBatches(1)).wait(); - const timestamp = (await hardhat.ethers.provider.getBlock('latest')).timestamp; - const systemLogs = createSystemLogs(); - systemLogs.push( - constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, - l2UpgradeTxHash - ) - ); - systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) - ); - - const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, - { - batchNumber: 2, - timestamp - }, - systemLogs - ); - - const commitReceipt = await ( - await proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx]) - ).wait(); - - expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(2); - const commitment = commitReceipt.events[0].args.commitment; - const newBatchStoredInfo = getBatchStoredInfo(batch2InfoTwoUpgradeTx, commitment); - await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); - - storedBatch1Info = newBatchStoredInfo; + expect(upgradeEvents[4].name).to.eq("NewL2DefaultAccountBytecodeHash"); + expect(upgradeEvents[4].args.previousBytecodeHash).to.eq( + "0x0100000100000000000000000000000000000000000000000000000000000000" + ); + expect(upgradeEvents[4].args.newBytecodeHash).to.eq(defaultAccountHash); + }); + + it("Should fail to upgrade when there is already a pending upgrade", async () => { + const bootloaderHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); + const defaultAccountHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); + const verifier = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + const verifierParams = buildVerifierParams({ + recursionNodeLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + recursionLeafLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + recursionCircuitsSetVksHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), }); - it('Should successfully commit a sequential upgrade', async () => { - expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); - await ( - await executeUpgrade(proxyGetters, proxyAdmin, { - newProtocolVersion: 5, - l2ProtocolUpgradeTx: noopUpgradeTransaction - }) - ).wait(); - - const timestamp = (await hardhat.ethers.provider.getBlock('latest')).timestamp; - const systemLogs = createSystemLogs(); - systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) - ); - - const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, - { - batchNumber: 3, - timestamp - }, - systemLogs - ); - - const commitReceipt = await ( - await proxyExecutor.commitBatches(storedBatch1Info, [batch3InfoTwoUpgradeTx]) - ).wait(); - const commitment = commitReceipt.events[0].args.commitment; - const newBatchStoredInfo = getBatchStoredInfo(batch3InfoTwoUpgradeTx, commitment); - - expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); - - await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); - - storedBatch1Info = newBatchStoredInfo; - - expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); + const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); + const myFactoryDepHash = hashBytecode(myFactoryDep); + const upgradeTx = buildL2CanonicalTransaction({ + factoryDeps: [myFactoryDepHash], + nonce: 4, }); - it('Should successfully commit custom upgrade', async () => { - const upgradeReceipt = await ( - await executeCustomUpgrade(proxyGetters, proxyAdmin, { - newProtocolVersion: 6, - l2ProtocolUpgradeTx: noopUpgradeTransaction - }) - ).wait(); - const customUpgradeFactory = await hardhat.ethers.getContractFactory('CustomUpgradeTest'); - - const upgradeEvents = upgradeReceipt.logs.map((log) => { - // Not all events can be parsed there, but we don't care about them - try { - const event = customUpgradeFactory.interface.parseLog(log); - const parsedArgs = event.args; - return { - name: event.name, - args: parsedArgs - }; - } catch (_) { - // @ts-ignore - } - }); - - const timestamp = (await hardhat.ethers.provider.getBlock('latest')).timestamp; - const systemLogs = createSystemLogs(); - systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, - ethers.utils.hexlify(storedBatch1Info.batchHash) - ); - - const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( - storedBatch1Info, - { - batchNumber: 4, - timestamp - }, - systemLogs - ); - - const commitReceipt = await ( - await proxyExecutor.commitBatches(storedBatch1Info, [batch3InfoTwoUpgradeTx]) - ).wait(); - const commitment = commitReceipt.events[0].args.commitment; - const newBatchStoredInfo = getBatchStoredInfo(batch3InfoTwoUpgradeTx, commitment); - - await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); - - storedBatch1Info = newBatchStoredInfo; - - expect(upgradeEvents[1].name).to.equal('Test'); + const upgrade = { + bootloaderHash, + defaultAccountHash, + verifier: verifier, + verifierParams, + executeUpgradeTx: true, + l2ProtocolUpgradeTx: upgradeTx, + factoryDeps: [myFactoryDep], + newProtocolVersion: 5, + }; + const revertReason = await getCallRevertReason(executeUpgrade(proxyGetters, proxyAdmin, upgrade)); + + expect(revertReason).to.equal("Previous upgrade has not been finalized"); + }); + + it("Should require that the next commit batches contains an upgrade tx", async () => { + if (!l2UpgradeTxHash) { + throw new Error("Can not perform this test without l2UpgradeTxHash"); + } + + const batch2InfoNoUpgradeTx = await buildCommitBatchInfo(storedBatch1Info, { + batchNumber: 2, }); -}); + const revertReason = await getCallRevertReason( + proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoNoUpgradeTx]) + ); + expect(revertReason).to.equal("b8"); + }); -type CommitBatchInfoWithTimestamp = Partial & { - batchNumber: BigNumberish; -}; + it("Should ensure any additional upgrade logs go to the priority ops hash", async () => { + if (!l2UpgradeTxHash) { + throw new Error("Can not perform this test without l2UpgradeTxHash"); + } -async function buildCommitBatchInfo( - prevInfo: StoredBatchInfo, - info: CommitBatchInfoWithTimestamp -): Promise { - const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock('latest')).timestamp; const systemLogs = createSystemLogs(); - systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( + systemLogs.push( + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, + l2UpgradeTxHash + ) + ); + systemLogs.push( + constructL2Log( true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - packBatchTimestampAndBatchTimestamp(timestamp, timestamp) + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, + l2UpgradeTxHash + ) + ); + systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + ethers.utils.hexlify(storedBatch1Info.batchHash) + ); + + const batch2InfoNoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch1Info, + { + batchNumber: 2, + }, + systemLogs + ); + const revertReason = await getCallRevertReason( + proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoNoUpgradeTx]) ); + expect(revertReason).to.equal("kp"); + }); - return { + it("Should fail to commit when upgrade tx hash does not match", async () => { + const timestamp = (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const systemLogs = createSystemLogs(); + systemLogs.push( + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, + ethers.constants.HashZero + ) + ); + systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + ethers.utils.hexlify(storedBatch1Info.batchHash) + ); + + const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch1Info, + { + batchNumber: 2, timestamp, - indexRepeatedStorageChanges: 0, - newStateRoot: ethers.utils.randomBytes(32), - numberOfLayer1Txs: 0, - priorityOperationsHash: EMPTY_STRING_KECCAK, - systemLogs: ethers.utils.hexConcat(systemLogs), - totalL2ToL1Pubdata: ethers.constants.HashZero, - bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), - eventsQueueStateHash: ethers.utils.randomBytes(32), - ...info - }; -} + }, + systemLogs + ); -async function buildCommitBatchInfoWithCustomLogs( - prevInfo: StoredBatchInfo, - info: CommitBatchInfoWithTimestamp, - systemLogs: string[] -): Promise { - const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock('latest')).timestamp; - systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( + const revertReason = await getCallRevertReason( + proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx]) + ); + expect(revertReason).to.equal("ut"); + }); + + it("Should commit successfully when the upgrade tx is present", async () => { + const timestamp = (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const systemLogs = createSystemLogs(); + systemLogs.push( + constructL2Log( true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - packBatchTimestampAndBatchTimestamp(timestamp, timestamp) + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, + l2UpgradeTxHash + ) + ); + systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + ethers.utils.hexlify(storedBatch1Info.batchHash) ); - return { + const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch1Info, + { + batchNumber: 2, timestamp, - indexRepeatedStorageChanges: 0, - newStateRoot: ethers.utils.randomBytes(32), - numberOfLayer1Txs: 0, - priorityOperationsHash: EMPTY_STRING_KECCAK, - systemLogs: ethers.utils.hexConcat(systemLogs), - totalL2ToL1Pubdata: ethers.constants.HashZero, - bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), - eventsQueueStateHash: ethers.utils.randomBytes(32), - ...info - }; + }, + systemLogs + ); + + await (await proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx])).wait(); + + expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(2); + }); + + it("Should commit successfully when batch was reverted and reupgraded", async () => { + await (await proxyExecutor.revertBatches(1)).wait(); + const timestamp = (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const systemLogs = createSystemLogs(); + systemLogs.push( + constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + SYSTEM_LOG_KEYS.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, + l2UpgradeTxHash + ) + ); + systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + ethers.utils.hexlify(storedBatch1Info.batchHash) + ); + + const batch2InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch1Info, + { + batchNumber: 2, + timestamp, + }, + systemLogs + ); + + const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch1Info, [batch2InfoTwoUpgradeTx])).wait(); + + expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(2); + const commitment = commitReceipt.events[0].args.commitment; + const newBatchStoredInfo = getBatchStoredInfo(batch2InfoTwoUpgradeTx, commitment); + await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); + + storedBatch1Info = newBatchStoredInfo; + }); + + it("Should successfully commit a sequential upgrade", async () => { + expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); + await ( + await executeUpgrade(proxyGetters, proxyAdmin, { + newProtocolVersion: 5, + l2ProtocolUpgradeTx: noopUpgradeTransaction, + }) + ).wait(); + + const timestamp = (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const systemLogs = createSystemLogs(); + systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + ethers.utils.hexlify(storedBatch1Info.batchHash) + ); + + const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch1Info, + { + batchNumber: 3, + timestamp, + }, + systemLogs + ); + + const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch1Info, [batch3InfoTwoUpgradeTx])).wait(); + const commitment = commitReceipt.events[0].args.commitment; + const newBatchStoredInfo = getBatchStoredInfo(batch3InfoTwoUpgradeTx, commitment); + + expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); + + await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); + + storedBatch1Info = newBatchStoredInfo; + + expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); + }); + + it("Should successfully commit custom upgrade", async () => { + const upgradeReceipt = await ( + await executeCustomUpgrade(proxyGetters, proxyAdmin, { + newProtocolVersion: 6, + l2ProtocolUpgradeTx: noopUpgradeTransaction, + }) + ).wait(); + const customUpgradeFactory = await hardhat.ethers.getContractFactory("CustomUpgradeTest"); + + const upgradeEvents = upgradeReceipt.logs.map((log) => { + // Not all events can be parsed there, but we don't care about them + try { + const event = customUpgradeFactory.interface.parseLog(log); + const parsedArgs = event.args; + return { + name: event.name, + args: parsedArgs, + }; + } catch (_) { + // @ts-ignore + } + }); + + const timestamp = (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const systemLogs = createSystemLogs(); + systemLogs[SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + ethers.utils.hexlify(storedBatch1Info.batchHash) + ); + + const batch3InfoTwoUpgradeTx = await buildCommitBatchInfoWithCustomLogs( + storedBatch1Info, + { + batchNumber: 4, + timestamp, + }, + systemLogs + ); + + const commitReceipt = await (await proxyExecutor.commitBatches(storedBatch1Info, [batch3InfoTwoUpgradeTx])).wait(); + const commitment = commitReceipt.events[0].args.commitment; + const newBatchStoredInfo = getBatchStoredInfo(batch3InfoTwoUpgradeTx, commitment); + + await makeExecutedEqualCommitted(proxyExecutor, storedBatch1Info, [newBatchStoredInfo], []); + + storedBatch1Info = newBatchStoredInfo; + + expect(upgradeEvents[1].name).to.equal("Test"); + }); +}); + +type CommitBatchInfoWithTimestamp = Partial & { + batchNumber: BigNumberish; +}; + +async function buildCommitBatchInfo( + prevInfo: StoredBatchInfo, + info: CommitBatchInfoWithTimestamp +): Promise { + const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; + const systemLogs = createSystemLogs(); + systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, + packBatchTimestampAndBatchTimestamp(timestamp, timestamp) + ); + + return { + timestamp, + indexRepeatedStorageChanges: 0, + newStateRoot: ethers.utils.randomBytes(32), + numberOfLayer1Txs: 0, + priorityOperationsHash: EMPTY_STRING_KECCAK, + systemLogs: ethers.utils.hexConcat(systemLogs), + totalL2ToL1Pubdata: ethers.constants.HashZero, + bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), + eventsQueueStateHash: ethers.utils.randomBytes(32), + ...info, + }; +} + +async function buildCommitBatchInfoWithCustomLogs( + prevInfo: StoredBatchInfo, + info: CommitBatchInfoWithTimestamp, + systemLogs: string[] +): Promise { + const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; + systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, + packBatchTimestampAndBatchTimestamp(timestamp, timestamp) + ); + + return { + timestamp, + indexRepeatedStorageChanges: 0, + newStateRoot: ethers.utils.randomBytes(32), + numberOfLayer1Txs: 0, + priorityOperationsHash: EMPTY_STRING_KECCAK, + systemLogs: ethers.utils.hexConcat(systemLogs), + totalL2ToL1Pubdata: ethers.constants.HashZero, + bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), + eventsQueueStateHash: ethers.utils.randomBytes(32), + ...info, + }; } function getBatchStoredInfo(commitInfo: CommitBatchInfo, commitment: string): StoredBatchInfo { - return { - batchNumber: commitInfo.batchNumber, - batchHash: commitInfo.newStateRoot, - indexRepeatedStorageChanges: commitInfo.indexRepeatedStorageChanges, - numberOfLayer1Txs: commitInfo.numberOfLayer1Txs, - priorityOperationsHash: commitInfo.priorityOperationsHash, - l2LogsTreeRoot: ethers.constants.HashZero, - timestamp: commitInfo.timestamp, - commitment: commitment - }; + return { + batchNumber: commitInfo.batchNumber, + batchHash: commitInfo.newStateRoot, + indexRepeatedStorageChanges: commitInfo.indexRepeatedStorageChanges, + numberOfLayer1Txs: commitInfo.numberOfLayer1Txs, + priorityOperationsHash: commitInfo.priorityOperationsHash, + l2LogsTreeRoot: ethers.constants.HashZero, + timestamp: commitInfo.timestamp, + commitment: commitment, + }; } interface L2CanonicalTransaction { - txType: BigNumberish; - from: BigNumberish; - to: BigNumberish; - gasLimit: BigNumberish; - gasPerPubdataByteLimit: BigNumberish; - maxFeePerGas: BigNumberish; - maxPriorityFeePerGas: BigNumberish; - paymaster: BigNumberish; - nonce: BigNumberish; - value: BigNumberish; - // In the future, we might want to add some - // new fields to the struct. The `txData` struct - // is to be passed to account and any changes to its structure - // would mean a breaking change to these accounts. In order to prevent this, - // we should keep some fields as "reserved". - // It is also recommended that their length is fixed, since - // it would allow easier proof integration (in case we will need - // some special circuit for preprocessing transactions). - reserved: [BigNumberish, BigNumberish, BigNumberish, BigNumberish]; - data: BytesLike; - signature: BytesLike; - factoryDeps: BigNumberish[]; - paymasterInput: BytesLike; - // Reserved dynamic type for the future use-case. Using it should be avoided, - // But it is still here, just in case we want to enable some additional functionality. - reservedDynamic: BytesLike; + txType: BigNumberish; + from: BigNumberish; + to: BigNumberish; + gasLimit: BigNumberish; + gasPerPubdataByteLimit: BigNumberish; + maxFeePerGas: BigNumberish; + maxPriorityFeePerGas: BigNumberish; + paymaster: BigNumberish; + nonce: BigNumberish; + value: BigNumberish; + // In the future, we might want to add some + // new fields to the struct. The `txData` struct + // is to be passed to account and any changes to its structure + // would mean a breaking change to these accounts. In order to prevent this, + // we should keep some fields as "reserved". + // It is also recommended that their length is fixed, since + // it would allow easier proof integration (in case we will need + // some special circuit for preprocessing transactions). + reserved: [BigNumberish, BigNumberish, BigNumberish, BigNumberish]; + data: BytesLike; + signature: BytesLike; + factoryDeps: BigNumberish[]; + paymasterInput: BytesLike; + // Reserved dynamic type for the future use-case. Using it should be avoided, + // But it is still here, just in case we want to enable some additional functionality. + reservedDynamic: BytesLike; } function buildL2CanonicalTransaction(tx: Partial): L2CanonicalTransaction { - return { - txType: SYSTEM_UPGRADE_TX_TYPE, - from: ethers.constants.AddressZero, - to: ethers.constants.AddressZero, - gasLimit: 5000000, - gasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - paymaster: 0, - nonce: 0, - value: 0, - reserved: [0, 0, 0, 0], - data: '0x', - signature: '0x', - factoryDeps: [], - paymasterInput: '0x', - reservedDynamic: '0x', - ...tx - }; + return { + txType: SYSTEM_UPGRADE_TX_TYPE, + from: ethers.constants.AddressZero, + to: ethers.constants.AddressZero, + gasLimit: 5000000, + gasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymaster: 0, + nonce: 0, + value: 0, + reserved: [0, 0, 0, 0], + data: "0x", + signature: "0x", + factoryDeps: [], + paymasterInput: "0x", + reservedDynamic: "0x", + ...tx, + }; } interface VerifierParams { - recursionNodeLevelVkHash: BytesLike; - recursionLeafLevelVkHash: BytesLike; - recursionCircuitsSetVksHash: BytesLike; + recursionNodeLevelVkHash: BytesLike; + recursionLeafLevelVkHash: BytesLike; + recursionCircuitsSetVksHash: BytesLike; } function buildVerifierParams(params: Partial): VerifierParams { - return { - recursionNodeLevelVkHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionCircuitsSetVksHash: ethers.constants.HashZero, - ...params - }; + return { + recursionNodeLevelVkHash: ethers.constants.HashZero, + recursionLeafLevelVkHash: ethers.constants.HashZero, + recursionCircuitsSetVksHash: ethers.constants.HashZero, + ...params, + }; } interface ProposedUpgrade { - // The tx for the upgrade call to the l2 system upgrade contract - l2ProtocolUpgradeTx: L2CanonicalTransaction; - factoryDeps: BytesLike[]; - executeUpgradeTx: boolean; - bootloaderHash: BytesLike; - defaultAccountHash: BytesLike; - verifier: string; - verifierParams: VerifierParams; - l1ContractsUpgradeCalldata: BytesLike; - postUpgradeCalldata: BytesLike; - upgradeTimestamp: ethers.BigNumber; - newProtocolVersion: BigNumberish; - newAllowList: string; + // The tx for the upgrade call to the l2 system upgrade contract + l2ProtocolUpgradeTx: L2CanonicalTransaction; + factoryDeps: BytesLike[]; + executeUpgradeTx: boolean; + bootloaderHash: BytesLike; + defaultAccountHash: BytesLike; + verifier: string; + verifierParams: VerifierParams; + l1ContractsUpgradeCalldata: BytesLike; + postUpgradeCalldata: BytesLike; + upgradeTimestamp: ethers.BigNumber; + newProtocolVersion: BigNumberish; + newAllowList: string; } type PartialProposedUpgrade = Partial; function buildProposeUpgrade(proposedUpgrade: PartialProposedUpgrade): ProposedUpgrade { - const newProtocolVersion = proposedUpgrade.newProtocolVersion || 0; - return { - l2ProtocolUpgradeTx: buildL2CanonicalTransaction({ nonce: newProtocolVersion }), - executeUpgradeTx: false, - bootloaderHash: ethers.constants.HashZero, - defaultAccountHash: ethers.constants.HashZero, - verifier: ethers.constants.AddressZero, - verifierParams: buildVerifierParams({}), - l1ContractsUpgradeCalldata: '0x', - postUpgradeCalldata: '0x', - upgradeTimestamp: ethers.constants.Zero, - factoryDeps: [], - newProtocolVersion, - newAllowList: ethers.constants.AddressZero, - ...proposedUpgrade - }; + const newProtocolVersion = proposedUpgrade.newProtocolVersion || 0; + return { + l2ProtocolUpgradeTx: buildL2CanonicalTransaction({ nonce: newProtocolVersion }), + executeUpgradeTx: false, + bootloaderHash: ethers.constants.HashZero, + defaultAccountHash: ethers.constants.HashZero, + verifier: ethers.constants.AddressZero, + verifierParams: buildVerifierParams({}), + l1ContractsUpgradeCalldata: "0x", + postUpgradeCalldata: "0x", + upgradeTimestamp: ethers.constants.Zero, + factoryDeps: [], + newProtocolVersion, + newAllowList: ethers.constants.AddressZero, + ...proposedUpgrade, + }; } async function executeUpgrade( - proxyGetters: GettersFacet, - proxyAdmin: AdminFacet, - partialUpgrade: Partial, - contractFactory?: ethers.ethers.ContractFactory + proxyGetters: GettersFacet, + proxyAdmin: AdminFacet, + partialUpgrade: Partial, + contractFactory?: ethers.ethers.ContractFactory ) { - if (partialUpgrade.newProtocolVersion == null) { - const newVersion = (await proxyGetters.getProtocolVersion()).add(1); - partialUpgrade.newProtocolVersion = newVersion; - } - const upgrade = buildProposeUpgrade(partialUpgrade); + if (partialUpgrade.newProtocolVersion == null) { + const newVersion = (await proxyGetters.getProtocolVersion()).add(1); + partialUpgrade.newProtocolVersion = newVersion; + } + const upgrade = buildProposeUpgrade(partialUpgrade); - const defaultUpgradeFactory = contractFactory - ? contractFactory - : await hardhat.ethers.getContractFactory('DefaultUpgrade'); + const defaultUpgradeFactory = contractFactory + ? contractFactory + : await hardhat.ethers.getContractFactory("DefaultUpgrade"); - const defaultUpgrade = await defaultUpgradeFactory.deploy(); - const diamondUpgradeInit = DefaultUpgradeFactory.connect(defaultUpgrade.address, defaultUpgrade.signer); + const defaultUpgrade = await defaultUpgradeFactory.deploy(); + const diamondUpgradeInit = DefaultUpgradeFactory.connect(defaultUpgrade.address, defaultUpgrade.signer); - const upgradeCalldata = diamondUpgradeInit.interface.encodeFunctionData('upgrade', [upgrade]); + const upgradeCalldata = diamondUpgradeInit.interface.encodeFunctionData("upgrade", [upgrade]); - const diamondCutData = diamondCut([], diamondUpgradeInit.address, upgradeCalldata); + const diamondCutData = diamondCut([], diamondUpgradeInit.address, upgradeCalldata); - // This promise will be handled in the tests - return proxyAdmin.executeUpgrade(diamondCutData); + // This promise will be handled in the tests + return proxyAdmin.executeUpgrade(diamondCutData); } async function executeCustomUpgrade( - proxyGetters: GettersFacet, - proxyAdmin: AdminFacet, - partialUpgrade: Partial, - contractFactory?: ethers.ethers.ContractFactory + proxyGetters: GettersFacet, + proxyAdmin: AdminFacet, + partialUpgrade: Partial, + contractFactory?: ethers.ethers.ContractFactory ) { - if (partialUpgrade.newProtocolVersion == null) { - const newVersion = (await proxyGetters.getProtocolVersion()).add(1); - partialUpgrade.newProtocolVersion = newVersion; - } - const upgrade = buildProposeUpgrade(partialUpgrade); + if (partialUpgrade.newProtocolVersion == null) { + const newVersion = (await proxyGetters.getProtocolVersion()).add(1); + partialUpgrade.newProtocolVersion = newVersion; + } + const upgrade = buildProposeUpgrade(partialUpgrade); - const upgradeFactory = contractFactory - ? contractFactory - : await hardhat.ethers.getContractFactory('CustomUpgradeTest'); + const upgradeFactory = contractFactory + ? contractFactory + : await hardhat.ethers.getContractFactory("CustomUpgradeTest"); - const customUpgrade = await upgradeFactory.deploy(); - const diamondUpgradeInit = CustomUpgradeTestFactory.connect(customUpgrade.address, customUpgrade.signer); + const customUpgrade = await upgradeFactory.deploy(); + const diamondUpgradeInit = CustomUpgradeTestFactory.connect(customUpgrade.address, customUpgrade.signer); - const upgradeCalldata = diamondUpgradeInit.interface.encodeFunctionData('upgrade', [upgrade]); + const upgradeCalldata = diamondUpgradeInit.interface.encodeFunctionData("upgrade", [upgrade]); - const diamondCutData = diamondCut([], diamondUpgradeInit.address, upgradeCalldata); + const diamondCutData = diamondCut([], diamondUpgradeInit.address, upgradeCalldata); - // This promise will be handled in the tests - return proxyAdmin.executeUpgrade(diamondCutData); + // This promise will be handled in the tests + return proxyAdmin.executeUpgrade(diamondCutData); } async function makeExecutedEqualCommitted( - proxyExecutor: ExecutorFacet, - prevBatchInfo: StoredBatchInfo, - batchesToProve: StoredBatchInfo[], - batchesToExecute: StoredBatchInfo[] + proxyExecutor: ExecutorFacet, + prevBatchInfo: StoredBatchInfo, + batchesToProve: StoredBatchInfo[], + batchesToExecute: StoredBatchInfo[] ) { - batchesToExecute = [...batchesToProve, ...batchesToExecute]; + batchesToExecute = [...batchesToProve, ...batchesToExecute]; - await ( - await proxyExecutor.proveBatches(prevBatchInfo, batchesToProve, { - recursiveAggregationInput: [], - serializedProof: [] - }) - ).wait(); + await ( + await proxyExecutor.proveBatches(prevBatchInfo, batchesToProve, { + recursiveAggregationInput: [], + serializedProof: [], + }) + ).wait(); - await (await proxyExecutor.executeBatches(batchesToExecute)).wait(); + await (await proxyExecutor.executeBatches(batchesToExecute)).wait(); } diff --git a/l1-contracts/test/unit_tests/mailbox_test.spec.ts b/l1-contracts/test/unit_tests/mailbox_test.spec.ts index a82adab14..ed928369d 100644 --- a/l1-contracts/test/unit_tests/mailbox_test.spec.ts +++ b/l1-contracts/test/unit_tests/mailbox_test.spec.ts @@ -1,422 +1,414 @@ -import { expect } from 'chai'; -import * as hardhat from 'hardhat'; -import { Action, facetCut, diamondCut } from '../../src.ts/diamondCut'; -import type { MailboxFacet, MockExecutorFacet, AllowList, Forwarder } from '../../typechain'; +import { expect } from "chai"; +import * as hardhat from "hardhat"; +import { Action, facetCut, diamondCut } from "../../src.ts/diamondCut"; +import type { MailboxFacet, MockExecutorFacet, AllowList, Forwarder } from "../../typechain"; import { - MailboxFacetFactory, - MockExecutorFacetFactory, - DiamondInitFactory, - AllowListFactory, - ForwarderFactory -} from '../../typechain'; + MailboxFacetFactory, + MockExecutorFacetFactory, + DiamondInitFactory, + AllowListFactory, + ForwarderFactory, +} from "../../typechain"; import { - DEFAULT_REVERT_REASON, - getCallRevertReason, - AccessMode, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - requestExecute -} from './utils'; -import * as ethers from 'ethers'; - -describe('Mailbox tests', function () { - let mailbox: MailboxFacet; - let proxyAsMockExecutor: MockExecutorFacet; - let allowList: AllowList; - let diamondProxyContract: ethers.Contract; - let owner: ethers.Signer; - let randomSigner: ethers.Signer; - const MAX_CODE_LEN_WORDS = (1 << 16) - 1; - const MAX_CODE_LEN_BYTES = MAX_CODE_LEN_WORDS * 32; - let forwarder: Forwarder; + DEFAULT_REVERT_REASON, + getCallRevertReason, + AccessMode, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + requestExecute, +} from "./utils"; +import * as ethers from "ethers"; + +describe("Mailbox tests", function () { + let mailbox: MailboxFacet; + let proxyAsMockExecutor: MockExecutorFacet; + let allowList: AllowList; + let diamondProxyContract: ethers.Contract; + let owner: ethers.Signer; + let randomSigner: ethers.Signer; + const MAX_CODE_LEN_WORDS = (1 << 16) - 1; + const MAX_CODE_LEN_BYTES = MAX_CODE_LEN_WORDS * 32; + let forwarder: Forwarder; + + before(async () => { + [owner, randomSigner] = await hardhat.ethers.getSigners(); + + const mailboxFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); + const mailboxContract = await mailboxFactory.deploy(); + const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); + + const mockExecutorFactory = await hardhat.ethers.getContractFactory("MockExecutorFacet"); + const mockExecutorContract = await mockExecutorFactory.deploy(); + const mockExecutorFacet = MockExecutorFacetFactory.connect( + mockExecutorContract.address, + mockExecutorContract.signer + ); + + const allowListFactory = await hardhat.ethers.getContractFactory("AllowList"); + const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); + allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); + + // Note, that while this testsuit is focused on testing MailboxFaucet only, + // we still need to initialize its storage via DiamondProxy + const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); + const diamondInitContract = await diamondInitFactory.deploy(); + const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); + + const dummyHash = new Uint8Array(32); + dummyHash.set([1, 0, 0, 1]); + const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + const diamondInitData = diamondInit.interface.encodeFunctionData("initialize", [ + { + verifier: dummyAddress, + governor: dummyAddress, + admin: dummyAddress, + genesisBatchHash: ethers.constants.HashZero, + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: ethers.constants.HashZero, + allowList: allowList.address, + verifierParams: { + recursionCircuitsSetVksHash: ethers.constants.HashZero, + recursionLeafLevelVkHash: ethers.constants.HashZero, + recursionNodeLevelVkHash: ethers.constants.HashZero, + }, + zkPorterIsAvailable: false, + l2BootloaderBytecodeHash: dummyHash, + l2DefaultAccountBytecodeHash: dummyHash, + priorityTxMaxGasLimit: 10000000, + initialProtocolVersion: 0, + }, + ]); + + const facetCuts = [ + facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, false), + facetCut(mockExecutorFacet.address, mockExecutorFacet.interface, Action.Add, false), + ]; + const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); + + const diamondProxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); + const chainId = hardhat.network.config.chainId; + diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); + + await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); + + mailbox = MailboxFacetFactory.connect(diamondProxyContract.address, mailboxContract.signer); + proxyAsMockExecutor = MockExecutorFacetFactory.connect(diamondProxyContract.address, mockExecutorContract.signer); + + const forwarderFactory = await hardhat.ethers.getContractFactory("Forwarder"); + const forwarderContract = await forwarderFactory.deploy(); + forwarder = ForwarderFactory.connect(forwarderContract.address, forwarderContract.signer); + }); + + it("Should accept correctly formatted bytecode", async () => { + const revertReason = await getCallRevertReason( + requestExecute( + mailbox, + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + ethers.BigNumber.from(1000000), + [new Uint8Array(32)], + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal(DEFAULT_REVERT_REASON); + }); + + it("Should not accept bytecode is not chunkable", async () => { + const revertReason = await getCallRevertReason( + requestExecute( + mailbox, + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + ethers.BigNumber.from(100000), + [new Uint8Array(63)], + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("pq"); + }); + + it("Should not accept bytecode of even length in words", async () => { + const revertReason = await getCallRevertReason( + requestExecute( + mailbox, + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + ethers.BigNumber.from(100000), + [new Uint8Array(64)], + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("ps"); + }); + + it("Should not accept bytecode that is too long", async () => { + const revertReason = await getCallRevertReason( + requestExecute( + mailbox, + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + ethers.BigNumber.from(100000), + [ + // "+64" to keep the length in words odd and bytecode chunkable + new Uint8Array(MAX_CODE_LEN_BYTES + 64), + ], + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("pp"); + }); + + describe("Deposit and Withdrawal limit functionality", function () { + const DEPOSIT_LIMIT = ethers.utils.parseEther("10"); before(async () => { - [owner, randomSigner] = await hardhat.ethers.getSigners(); - - const mailboxFactory = await hardhat.ethers.getContractFactory('MailboxFacet'); - const mailboxContract = await mailboxFactory.deploy(); - const mailboxFacet = MailboxFacetFactory.connect(mailboxContract.address, mailboxContract.signer); - - const mockExecutorFactory = await hardhat.ethers.getContractFactory('MockExecutorFacet'); - const mockExecutorContract = await mockExecutorFactory.deploy(); - const mockExecutorFacet = MockExecutorFacetFactory.connect( - mockExecutorContract.address, - mockExecutorContract.signer - ); - - const allowListFactory = await hardhat.ethers.getContractFactory('AllowList'); - const allowListContract = await allowListFactory.deploy(await allowListFactory.signer.getAddress()); - allowList = AllowListFactory.connect(allowListContract.address, allowListContract.signer); - - // Note, that while this testsuit is focused on testing MailboxFaucet only, - // we still need to initialize its storage via DiamondProxy - const diamondInitFactory = await hardhat.ethers.getContractFactory('DiamondInit'); - const diamondInitContract = await diamondInitFactory.deploy(); - const diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const dummyHash = new Uint8Array(32); - dummyHash.set([1, 0, 0, 1]); - const dummyAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - const diamondInitData = diamondInit.interface.encodeFunctionData('initialize', [ - { - verifier: dummyAddress, - governor: dummyAddress, - admin: dummyAddress, - genesisBatchHash: ethers.constants.HashZero, - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - allowList: allowList.address, - verifierParams: { - recursionCircuitsSetVksHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionNodeLevelVkHash: ethers.constants.HashZero - }, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: dummyHash, - l2DefaultAccountBytecodeHash: dummyHash, - priorityTxMaxGasLimit: 10000000, - initialProtocolVersion: 0 - } - ]); - - const facetCuts = [ - facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, false), - facetCut(mockExecutorFacet.address, mockExecutorFacet.interface, Action.Add, false) - ]; - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitData); - - const diamondProxyFactory = await hardhat.ethers.getContractFactory('DiamondProxy'); - const chainId = hardhat.network.config.chainId; - diamondProxyContract = await diamondProxyFactory.deploy(chainId, diamondCutData); - - await (await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Public)).wait(); - - mailbox = MailboxFacetFactory.connect(diamondProxyContract.address, mailboxContract.signer); - proxyAsMockExecutor = MockExecutorFacetFactory.connect( - diamondProxyContract.address, - mockExecutorContract.signer - ); - - const forwarderFactory = await hardhat.ethers.getContractFactory('Forwarder'); - const forwarderContract = await forwarderFactory.deploy(); - forwarder = ForwarderFactory.connect(forwarderContract.address, forwarderContract.signer); + await allowList.setDepositLimit(ethers.constants.AddressZero, true, DEPOSIT_LIMIT); }); - it('Should accept correctly formatted bytecode', async () => { - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - '0x', - ethers.BigNumber.from(1000000), - [new Uint8Array(32)], - ethers.constants.AddressZero - ) - ); + it("Should not accept depositing more than the deposit limit", async () => { + const revertReason = await getCallRevertReason( + requestExecute( + mailbox, + ethers.constants.AddressZero, + ethers.utils.parseEther("12"), + "0x", + ethers.BigNumber.from(100000), + [new Uint8Array(32)], + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("d2"); + }); - expect(revertReason).equal(DEFAULT_REVERT_REASON); + it("Should accept depositing less than or equal to the deposit limit", async () => { + const gasPrice = await mailbox.provider.getGasPrice(); + const l2GasLimit = ethers.BigNumber.from(1000000); + const l2Cost = await mailbox.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); + + const revertReason = await getCallRevertReason( + requestExecute( + mailbox, + ethers.constants.AddressZero, + DEPOSIT_LIMIT.sub(l2Cost), + "0x", + l2GasLimit, + [new Uint8Array(32)], + ethers.constants.AddressZero, + { gasPrice } + ) + ); + + expect(revertReason).equal(DEFAULT_REVERT_REASON); }); - it('Should not accept bytecode is not chunkable', async () => { - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - '0x', - ethers.BigNumber.from(100000), - [new Uint8Array(63)], - ethers.constants.AddressZero - ) - ); + it("Should not accept depositing that the accumulation is more than the deposit limit", async () => { + const revertReason = await getCallRevertReason( + requestExecute( + mailbox, + ethers.constants.AddressZero, + ethers.BigNumber.from(1), + "0x", + ethers.BigNumber.from(1000000), + [new Uint8Array(32)], + ethers.constants.AddressZero + ) + ); + + expect(revertReason).equal("d2"); + }); + }); + + describe("finalizeEthWithdrawal", function () { + const BLOCK_NUMBER = 1; + const MESSAGE_INDEX = 0; + const TX_NUMBER_IN_BLOCK = 0; + const L1_RECEIVER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + const AMOUNT = 1; + const MESSAGE = + "0x6c0960f9d8dA6BF26964aF9D7eEd9e03E53415D37aA960450000000000000000000000000000000000000000000000000000000000000001"; + // MESSAGE_HASH = 0xf55ef1c502bb79468b8ffe79955af4557a068ec4894e2207010866b182445c52 + // HASHED_LOG = 0x110c937a27f7372384781fe744c2e971daa9556b1810f2edea90fb8b507f84b1 + const L2_LOGS_TREE_ROOT = "0xfa6b5a02c911a05e9dfe9e03f6dedb9cd30795bbac2aaf5bdd2632d2671a7e3d"; + const MERKLE_PROOF = [ + "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba", + "0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0", + "0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111", + "0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa", + "0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89", + "0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272", + "0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d", + "0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c", + "0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db", + ]; - expect(revertReason).equal('pq'); + before(async () => { + await proxyAsMockExecutor.saveL2LogsRootHash(BLOCK_NUMBER, L2_LOGS_TREE_ROOT); }); - it('Should not accept bytecode of even length in words', async () => { - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - '0x', - ethers.BigNumber.from(100000), - [new Uint8Array(64)], - ethers.constants.AddressZero - ) - ); + it("Reverts when proof is invalid", async () => { + const invalidProof = [...MERKLE_PROOF]; + invalidProof[0] = "0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43bb"; - expect(revertReason).equal('ps'); + const revertReason = await getCallRevertReason( + mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, invalidProof) + ); + expect(revertReason).equal("pi"); }); - it('Should not accept bytecode that is too long', async () => { - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - '0x', - ethers.BigNumber.from(100000), - [ - // "+64" to keep the length in words odd and bytecode chunkable - new Uint8Array(MAX_CODE_LEN_BYTES + 64) - ], - ethers.constants.AddressZero - ) - ); + it("Successful withdrawal", async () => { + const balanceBefore = await hardhat.ethers.provider.getBalance(L1_RECEIVER); - expect(revertReason).equal('pp'); - }); + await mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF); - describe('Deposit and Withdrawal limit functionality', function () { - const DEPOSIT_LIMIT = ethers.utils.parseEther('10'); - - before(async () => { - await allowList.setDepositLimit(ethers.constants.AddressZero, true, DEPOSIT_LIMIT); - }); - - it('Should not accept depositing more than the deposit limit', async () => { - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - ethers.utils.parseEther('12'), - '0x', - ethers.BigNumber.from(100000), - [new Uint8Array(32)], - ethers.constants.AddressZero - ) - ); - - expect(revertReason).equal('d2'); - }); - - it('Should accept depositing less than or equal to the deposit limit', async () => { - const gasPrice = await mailbox.provider.getGasPrice(); - const l2GasLimit = ethers.BigNumber.from(1000000); - const l2Cost = await mailbox.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); - - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - DEPOSIT_LIMIT.sub(l2Cost), - '0x', - l2GasLimit, - [new Uint8Array(32)], - ethers.constants.AddressZero, - { gasPrice } - ) - ); - - expect(revertReason).equal(DEFAULT_REVERT_REASON); - }); - - it('Should not accept depositing that the accumulation is more than the deposit limit', async () => { - const revertReason = await getCallRevertReason( - requestExecute( - mailbox, - ethers.constants.AddressZero, - ethers.BigNumber.from(1), - '0x', - ethers.BigNumber.from(1000000), - [new Uint8Array(32)], - ethers.constants.AddressZero - ) - ); - - expect(revertReason).equal('d2'); - }); + const balanceAfter = await hardhat.ethers.provider.getBalance(L1_RECEIVER); + expect(balanceAfter.sub(balanceBefore)).equal(AMOUNT); }); - describe('finalizeEthWithdrawal', function () { - const BLOCK_NUMBER = 1; - const MESSAGE_INDEX = 0; - const TX_NUMBER_IN_BLOCK = 0; - const L1_RECEIVER = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; - const AMOUNT = 1; - const MESSAGE = - '0x6c0960f9d8dA6BF26964aF9D7eEd9e03E53415D37aA960450000000000000000000000000000000000000000000000000000000000000001'; - // MESSAGE_HASH = 0xf55ef1c502bb79468b8ffe79955af4557a068ec4894e2207010866b182445c52 - // HASHED_LOG = 0x110c937a27f7372384781fe744c2e971daa9556b1810f2edea90fb8b507f84b1 - const L2_LOGS_TREE_ROOT = '0xfa6b5a02c911a05e9dfe9e03f6dedb9cd30795bbac2aaf5bdd2632d2671a7e3d'; - const MERKLE_PROOF = [ - '0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba', - '0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0', - '0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111', - '0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa', - '0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89', - '0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272', - '0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d', - '0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c', - '0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db' - ]; - - before(async () => { - await proxyAsMockExecutor.saveL2LogsRootHash(BLOCK_NUMBER, L2_LOGS_TREE_ROOT); - }); - - it('Reverts when proof is invalid', async () => { - const invalidProof = [...MERKLE_PROOF]; - invalidProof[0] = '0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43bb'; - - const revertReason = await getCallRevertReason( - mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, invalidProof) - ); - expect(revertReason).equal('pi'); - }); - - it('Successful withdrawal', async () => { - const balanceBefore = await hardhat.ethers.provider.getBalance(L1_RECEIVER); - - await mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF); - - const balanceAfter = await hardhat.ethers.provider.getBalance(L1_RECEIVER); - expect(balanceAfter.sub(balanceBefore)).equal(AMOUNT); - }); - - it('Reverts when withdrawal is already finalized', async () => { - const revertReason = await getCallRevertReason( - mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF) - ); - expect(revertReason).equal('jj'); - }); + it("Reverts when withdrawal is already finalized", async () => { + const revertReason = await getCallRevertReason( + mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF) + ); + expect(revertReason).equal("jj"); }); + }); - describe('Access mode functionality', function () { - before(async () => { - // We still need to set infinite amount of allowed deposit limit in order to ensure that every fee will be accepted - await allowList.setDepositLimit(ethers.constants.AddressZero, true, ethers.utils.parseEther('2000')); - }); - - it('Should not allow an un-whitelisted address to call', async () => { - await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Closed); - - const revertReason = await getCallRevertReason( - requestExecute( - mailbox.connect(randomSigner), - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - '0x', - ethers.BigNumber.from(100000), - [new Uint8Array(32)], - ethers.constants.AddressZero - ) - ); - expect(revertReason).equal('nr'); - }); - - it('Should allow the whitelisted address to call', async () => { - await allowList.setAccessMode(diamondProxyContract.address, AccessMode.SpecialAccessOnly); - await allowList.setPermissionToCall( - await owner.getAddress(), - diamondProxyContract.address, - '0xeb672419', - true - ); - - const revertReason = await getCallRevertReason( - requestExecute( - mailbox.connect(owner), - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - '0x', - ethers.BigNumber.from(1000000), - [new Uint8Array(32)], - ethers.constants.AddressZero - ) - ); - expect(revertReason).equal(DEFAULT_REVERT_REASON); - }); + describe("Access mode functionality", function () { + before(async () => { + // We still need to set infinite amount of allowed deposit limit in order to ensure that every fee will be accepted + await allowList.setDepositLimit(ethers.constants.AddressZero, true, ethers.utils.parseEther("2000")); }); - let callDirectly, callViaForwarder, callViaConstructorForwarder; - - before(async () => { - const l2GasLimit = ethers.BigNumber.from(10000000); - - callDirectly = async (refundRecipient) => { - return { - transaction: await requestExecute( - mailbox.connect(owner), - ethers.constants.AddressZero, - ethers.BigNumber.from(0), - '0x', - l2GasLimit, - [new Uint8Array(32)], - refundRecipient - ), - expectedSender: await owner.getAddress() - }; - }; - - const encodeRequest = (refundRecipient) => - mailbox.interface.encodeFunctionData('requestL2Transaction', [ - ethers.constants.AddressZero, - 0, - '0x', - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [new Uint8Array(32)], - refundRecipient - ]); - - const overrides: ethers.PayableOverrides = {}; - overrides.gasPrice = await mailbox.provider.getGasPrice(); - overrides.value = await mailbox.l2TransactionBaseCost( - overrides.gasPrice, - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - overrides.gasLimit = 10000000; - - callViaForwarder = async (refundRecipient) => { - return { - transaction: await forwarder.forward(mailbox.address, encodeRequest(refundRecipient), overrides), - expectedSender: aliasAddress(forwarder.address) - }; - }; - - callViaConstructorForwarder = async (refundRecipient) => { - const constructorForwarder = await ( - await hardhat.ethers.getContractFactory('ConstructorForwarder') - ).deploy(mailbox.address, encodeRequest(refundRecipient), overrides); - - return { - transaction: constructorForwarder.deployTransaction, - expectedSender: aliasAddress(constructorForwarder.address) - }; - }; + it("Should not allow an un-whitelisted address to call", async () => { + await allowList.setAccessMode(diamondProxyContract.address, AccessMode.Closed); + + const revertReason = await getCallRevertReason( + requestExecute( + mailbox.connect(randomSigner), + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + ethers.BigNumber.from(100000), + [new Uint8Array(32)], + ethers.constants.AddressZero + ) + ); + expect(revertReason).equal("nr"); }); - it('Should only alias externally-owned addresses', async () => { - const indirections = [callDirectly, callViaForwarder, callViaConstructorForwarder]; - const refundRecipients = [ - [mailbox.address, false], - [await mailbox.signer.getAddress(), true] - ]; - - for (const sendTransaction of indirections) { - for (const [refundRecipient, externallyOwned] of refundRecipients) { - const result = await sendTransaction(refundRecipient); - - const [event] = (await result.transaction.wait()).logs; - const parsedEvent = mailbox.interface.parseLog(event); - expect(parsedEvent.name).to.equal('NewPriorityRequest'); - - const canonicalTransaction = parsedEvent.args.transaction; - expect(canonicalTransaction.from).to.equal(result.expectedSender); - - expect(canonicalTransaction.reserved[1]).to.equal( - externallyOwned ? refundRecipient : aliasAddress(refundRecipient) - ); - } - } + it("Should allow the whitelisted address to call", async () => { + await allowList.setAccessMode(diamondProxyContract.address, AccessMode.SpecialAccessOnly); + await allowList.setPermissionToCall(await owner.getAddress(), diamondProxyContract.address, "0xeb672419", true); + + const revertReason = await getCallRevertReason( + requestExecute( + mailbox.connect(owner), + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + ethers.BigNumber.from(1000000), + [new Uint8Array(32)], + ethers.constants.AddressZero + ) + ); + expect(revertReason).equal(DEFAULT_REVERT_REASON); }); + }); + + let callDirectly, callViaForwarder, callViaConstructorForwarder; + + before(async () => { + const l2GasLimit = ethers.BigNumber.from(10000000); + + callDirectly = async (refundRecipient) => { + return { + transaction: await requestExecute( + mailbox.connect(owner), + ethers.constants.AddressZero, + ethers.BigNumber.from(0), + "0x", + l2GasLimit, + [new Uint8Array(32)], + refundRecipient + ), + expectedSender: await owner.getAddress(), + }; + }; + + const encodeRequest = (refundRecipient) => + mailbox.interface.encodeFunctionData("requestL2Transaction", [ + ethers.constants.AddressZero, + 0, + "0x", + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + [new Uint8Array(32)], + refundRecipient, + ]); + + const overrides: ethers.PayableOverrides = {}; + overrides.gasPrice = await mailbox.provider.getGasPrice(); + overrides.value = await mailbox.l2TransactionBaseCost( + overrides.gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ); + overrides.gasLimit = 10000000; + + callViaForwarder = async (refundRecipient) => { + return { + transaction: await forwarder.forward(mailbox.address, encodeRequest(refundRecipient), overrides), + expectedSender: aliasAddress(forwarder.address), + }; + }; + + callViaConstructorForwarder = async (refundRecipient) => { + const constructorForwarder = await ( + await hardhat.ethers.getContractFactory("ConstructorForwarder") + ).deploy(mailbox.address, encodeRequest(refundRecipient), overrides); + + return { + transaction: constructorForwarder.deployTransaction, + expectedSender: aliasAddress(constructorForwarder.address), + }; + }; + }); + + it("Should only alias externally-owned addresses", async () => { + const indirections = [callDirectly, callViaForwarder, callViaConstructorForwarder]; + const refundRecipients = [ + [mailbox.address, false], + [await mailbox.signer.getAddress(), true], + ]; + + for (const sendTransaction of indirections) { + for (const [refundRecipient, externallyOwned] of refundRecipients) { + const result = await sendTransaction(refundRecipient); + + const [event] = (await result.transaction.wait()).logs; + const parsedEvent = mailbox.interface.parseLog(event); + expect(parsedEvent.name).to.equal("NewPriorityRequest"); + + const canonicalTransaction = parsedEvent.args.transaction; + expect(canonicalTransaction.from).to.equal(result.expectedSender); + + expect(canonicalTransaction.reserved[1]).to.equal( + externallyOwned ? refundRecipient : aliasAddress(refundRecipient) + ); + } + } + }); }); function aliasAddress(address) { - return ethers.BigNumber.from(address) - .add('0x1111000000000000000000000000000000001111') - .mask(20 * 8); + return ethers.BigNumber.from(address) + .add("0x1111000000000000000000000000000000001111") + .mask(20 * 8); } diff --git a/l1-contracts/test/unit_tests/merkle_test.spec.ts b/l1-contracts/test/unit_tests/merkle_test.spec.ts index e069d8f39..9ba239317 100644 --- a/l1-contracts/test/unit_tests/merkle_test.spec.ts +++ b/l1-contracts/test/unit_tests/merkle_test.spec.ts @@ -1,69 +1,69 @@ -import { expect } from 'chai'; -import * as hardhat from 'hardhat'; -import type { MerkleTest } from '../../typechain'; -import { MerkleTestFactory } from '../../typechain'; -import { MerkleTree } from 'merkletreejs'; -import { getCallRevertReason } from './utils'; -import * as ethers from 'ethers'; +import { expect } from "chai"; +import * as hardhat from "hardhat"; +import type { MerkleTest } from "../../typechain"; +import { MerkleTestFactory } from "../../typechain"; +import { MerkleTree } from "merkletreejs"; +import { getCallRevertReason } from "./utils"; +import * as ethers from "ethers"; -describe('Merkle lib tests', function () { - let merkleTest: MerkleTest; +describe("Merkle lib tests", function () { + let merkleTest: MerkleTest; - before(async () => { - const contractFactory = await hardhat.ethers.getContractFactory('MerkleTest'); - const contract = await contractFactory.deploy(); - merkleTest = MerkleTestFactory.connect(contract.address, contract.signer); - }); + before(async () => { + const contractFactory = await hardhat.ethers.getContractFactory("MerkleTest"); + const contract = await contractFactory.deploy(); + merkleTest = MerkleTestFactory.connect(contract.address, contract.signer); + }); - describe('should calculate root correctly', function () { - let elements; - let merkleTree; + describe("should calculate root correctly", function () { + let elements; + let merkleTree; - before(async () => { - elements = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - .split('') - .map((val) => ethers.utils.toUtf8Bytes(val)); - merkleTree = new MerkleTree(elements, ethers.utils.keccak256, { hashLeaves: true }); - }); + before(async () => { + elements = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + .split("") + .map((val) => ethers.utils.toUtf8Bytes(val)); + merkleTree = new MerkleTree(elements, ethers.utils.keccak256, { hashLeaves: true }); + }); - it('first element', async () => { - const index = 0; - const leaf = ethers.utils.keccak256(elements[index]); - const proof = merkleTree.getHexProof(leaf, index); + it("first element", async () => { + const index = 0; + const leaf = ethers.utils.keccak256(elements[index]); + const proof = merkleTree.getHexProof(leaf, index); - const rootFromContract = await merkleTest.calculateRoot(proof, index, leaf); - expect(rootFromContract).to.equal(merkleTree.getHexRoot()); - }); + const rootFromContract = await merkleTest.calculateRoot(proof, index, leaf); + expect(rootFromContract).to.equal(merkleTree.getHexRoot()); + }); - it('middle element', async () => { - const index = Math.ceil(elements.length / 2); - const leaf = ethers.utils.keccak256(elements[index]); - const proof = merkleTree.getHexProof(leaf, index); + it("middle element", async () => { + const index = Math.ceil(elements.length / 2); + const leaf = ethers.utils.keccak256(elements[index]); + const proof = merkleTree.getHexProof(leaf, index); - const rootFromContract = await merkleTest.calculateRoot(proof, index, leaf); - expect(rootFromContract).to.equal(merkleTree.getHexRoot()); - }); + const rootFromContract = await merkleTest.calculateRoot(proof, index, leaf); + expect(rootFromContract).to.equal(merkleTree.getHexRoot()); + }); - it('last element', async () => { - const index = elements.length - 1; - const leaf = ethers.utils.keccak256(elements[index]); - const proof = merkleTree.getHexProof(leaf, index); + it("last element", async () => { + const index = elements.length - 1; + const leaf = ethers.utils.keccak256(elements[index]); + const proof = merkleTree.getHexProof(leaf, index); - const rootFromContract = await merkleTest.calculateRoot(proof, index, leaf); - expect(rootFromContract).to.equal(merkleTree.getHexRoot()); - }); + const rootFromContract = await merkleTest.calculateRoot(proof, index, leaf); + expect(rootFromContract).to.equal(merkleTree.getHexRoot()); }); + }); - it('should fail trying calculate root with empty path', async () => { - const revertReason = await getCallRevertReason(merkleTest.calculateRoot([], 0, ethers.constants.HashZero)); - expect(revertReason).equal('xc'); - }); + it("should fail trying calculate root with empty path", async () => { + const revertReason = await getCallRevertReason(merkleTest.calculateRoot([], 0, ethers.constants.HashZero)); + expect(revertReason).equal("xc"); + }); - it('should fail trying calculate root with too big leaf index', async () => { - const bigIndex = ethers.BigNumber.from(2).pow(255); - const revertReason = await getCallRevertReason( - merkleTest.calculateRoot([ethers.constants.HashZero], bigIndex, ethers.constants.HashZero) - ); - expect(revertReason).equal('px'); - }); + it("should fail trying calculate root with too big leaf index", async () => { + const bigIndex = ethers.BigNumber.from(2).pow(255); + const revertReason = await getCallRevertReason( + merkleTest.calculateRoot([ethers.constants.HashZero], bigIndex, ethers.constants.HashZero) + ); + expect(revertReason).equal("px"); + }); }); diff --git a/l1-contracts/test/unit_tests/priority_queue_test.spec.ts b/l1-contracts/test/unit_tests/priority_queue_test.spec.ts index 79ec79c03..a59c1e2f1 100644 --- a/l1-contracts/test/unit_tests/priority_queue_test.spec.ts +++ b/l1-contracts/test/unit_tests/priority_queue_test.spec.ts @@ -1,133 +1,133 @@ -import { expect } from 'chai'; -import * as hardhat from 'hardhat'; -import { ethers } from 'hardhat'; -import type { PriorityQueueTest } from '../../typechain'; -import { PriorityQueueTestFactory } from '../../typechain'; -import { getCallRevertReason } from './utils'; +import { expect } from "chai"; +import * as hardhat from "hardhat"; +import { ethers } from "hardhat"; +import type { PriorityQueueTest } from "../../typechain"; +import { PriorityQueueTestFactory } from "../../typechain"; +import { getCallRevertReason } from "./utils"; + +describe("Priority queue tests", function () { + let priorityQueueTest: PriorityQueueTest; + const queue = []; + + before(async () => { + const contractFactory = await hardhat.ethers.getContractFactory("PriorityQueueTest"); + const contract = await contractFactory.deploy(); + priorityQueueTest = PriorityQueueTestFactory.connect(contract.address, contract.signer); + }); + + describe("on empty queue", function () { + it("getSize", async () => { + const size = await priorityQueueTest.getSize(); + expect(size).equal(0); + }); + + it("getFirstUnprocessedPriorityTx", async () => { + const firstUnprocessedTx = await priorityQueueTest.getFirstUnprocessedPriorityTx(); + expect(firstUnprocessedTx).equal(0); + }); + + it("getTotalPriorityTxs", async () => { + const totalPriorityTxs = await priorityQueueTest.getTotalPriorityTxs(); + expect(totalPriorityTxs).equal(0); + }); -describe('Priority queue tests', function () { - let priorityQueueTest: PriorityQueueTest; - const queue = []; + it("isEmpty", async () => { + const isEmpty = await priorityQueueTest.isEmpty(); + expect(isEmpty).equal(true); + }); + + it("failed to get front", async () => { + const revertReason = await getCallRevertReason(priorityQueueTest.front()); + expect(revertReason).equal("D"); + }); + + it("failed to pop", async () => { + const revertReason = await getCallRevertReason(priorityQueueTest.popFront()); + expect(revertReason).equal("s"); + }); + }); + + describe("push operations", function () { + const NUMBER_OPERATIONS = 10; before(async () => { - const contractFactory = await hardhat.ethers.getContractFactory('PriorityQueueTest'); - const contract = await contractFactory.deploy(); - priorityQueueTest = PriorityQueueTestFactory.connect(contract.address, contract.signer); - }); - - describe('on empty queue', function () { - it('getSize', async () => { - const size = await priorityQueueTest.getSize(); - expect(size).equal(0); - }); - - it('getFirstUnprocessedPriorityTx', async () => { - const firstUnprocessedTx = await priorityQueueTest.getFirstUnprocessedPriorityTx(); - expect(firstUnprocessedTx).equal(0); - }); - - it('getTotalPriorityTxs', async () => { - const totalPriorityTxs = await priorityQueueTest.getTotalPriorityTxs(); - expect(totalPriorityTxs).equal(0); - }); - - it('isEmpty', async () => { - const isEmpty = await priorityQueueTest.isEmpty(); - expect(isEmpty).equal(true); - }); - - it('failed to get front', async () => { - const revertReason = await getCallRevertReason(priorityQueueTest.front()); - expect(revertReason).equal('D'); - }); - - it('failed to pop', async () => { - const revertReason = await getCallRevertReason(priorityQueueTest.popFront()); - expect(revertReason).equal('s'); - }); - }); - - describe('push operations', function () { - const NUMBER_OPERATIONS = 10; - - before(async () => { - for (let i = 0; i < NUMBER_OPERATIONS; ++i) { - const dummyOp = { canonicalTxHash: ethers.constants.HashZero, expirationTimestamp: i, layer2Tip: i }; - queue.push(dummyOp); - await priorityQueueTest.pushBack(dummyOp); - } - }); - - it('front', async () => { - const frontElement = await priorityQueueTest.front(); - - expect(frontElement.canonicalTxHash).equal(queue[0].canonicalTxHash); - expect(frontElement.expirationTimestamp).equal(queue[0].expirationTimestamp); - expect(frontElement.layer2Tip).equal(queue[0].layer2Tip); - }); - - it('getSize', async () => { - const size = await priorityQueueTest.getSize(); - expect(size).equal(queue.length); - }); - - it('getFirstUnprocessedPriorityTx', async () => { - const firstUnprocessedTx = await priorityQueueTest.getFirstUnprocessedPriorityTx(); - expect(firstUnprocessedTx).equal(0); - }); - - it('getTotalPriorityTxs', async () => { - const totalPriorityTxs = await priorityQueueTest.getTotalPriorityTxs(); - expect(totalPriorityTxs).equal(queue.length); - }); - - it('isEmpty', async () => { - const isEmpty = await priorityQueueTest.isEmpty(); - expect(isEmpty).equal(false); - }); - }); - - describe('pop operations', function () { - const NUMBER_OPERATIONS = 4; - - before(async () => { - for (let i = 0; i < NUMBER_OPERATIONS; ++i) { - const frontElement = await priorityQueueTest.front(); - expect(frontElement.canonicalTxHash).equal(queue[0].canonicalTxHash); - expect(frontElement.expirationTimestamp).equal(queue[0].expirationTimestamp); - expect(frontElement.layer2Tip).equal(queue[0].layer2Tip); - - await priorityQueueTest.popFront(); - queue.shift(); - } - }); - - it('front', async () => { - const frontElement = await priorityQueueTest.front(); - - expect(frontElement.canonicalTxHash).equal(queue[0].canonicalTxHash); - expect(frontElement.expirationTimestamp).equal(queue[0].expirationTimestamp); - expect(frontElement.layer2Tip).equal(queue[0].layer2Tip); - }); - - it('getSize', async () => { - const size = await priorityQueueTest.getSize(); - expect(size).equal(queue.length); - }); - - it('getFirstUnprocessedPriorityTx', async () => { - const firstUnprocessedTx = await priorityQueueTest.getFirstUnprocessedPriorityTx(); - expect(firstUnprocessedTx).equal(NUMBER_OPERATIONS); - }); - - it('getTotalPriorityTxs', async () => { - const totalPriorityTxs = await priorityQueueTest.getTotalPriorityTxs(); - expect(totalPriorityTxs).equal(queue.length + NUMBER_OPERATIONS); - }); - - it('isEmpty', async () => { - const isEmpty = await priorityQueueTest.isEmpty(); - expect(isEmpty).equal(false); - }); + for (let i = 0; i < NUMBER_OPERATIONS; ++i) { + const dummyOp = { canonicalTxHash: ethers.constants.HashZero, expirationTimestamp: i, layer2Tip: i }; + queue.push(dummyOp); + await priorityQueueTest.pushBack(dummyOp); + } + }); + + it("front", async () => { + const frontElement = await priorityQueueTest.front(); + + expect(frontElement.canonicalTxHash).equal(queue[0].canonicalTxHash); + expect(frontElement.expirationTimestamp).equal(queue[0].expirationTimestamp); + expect(frontElement.layer2Tip).equal(queue[0].layer2Tip); + }); + + it("getSize", async () => { + const size = await priorityQueueTest.getSize(); + expect(size).equal(queue.length); + }); + + it("getFirstUnprocessedPriorityTx", async () => { + const firstUnprocessedTx = await priorityQueueTest.getFirstUnprocessedPriorityTx(); + expect(firstUnprocessedTx).equal(0); + }); + + it("getTotalPriorityTxs", async () => { + const totalPriorityTxs = await priorityQueueTest.getTotalPriorityTxs(); + expect(totalPriorityTxs).equal(queue.length); + }); + + it("isEmpty", async () => { + const isEmpty = await priorityQueueTest.isEmpty(); + expect(isEmpty).equal(false); + }); + }); + + describe("pop operations", function () { + const NUMBER_OPERATIONS = 4; + + before(async () => { + for (let i = 0; i < NUMBER_OPERATIONS; ++i) { + const frontElement = await priorityQueueTest.front(); + expect(frontElement.canonicalTxHash).equal(queue[0].canonicalTxHash); + expect(frontElement.expirationTimestamp).equal(queue[0].expirationTimestamp); + expect(frontElement.layer2Tip).equal(queue[0].layer2Tip); + + await priorityQueueTest.popFront(); + queue.shift(); + } + }); + + it("front", async () => { + const frontElement = await priorityQueueTest.front(); + + expect(frontElement.canonicalTxHash).equal(queue[0].canonicalTxHash); + expect(frontElement.expirationTimestamp).equal(queue[0].expirationTimestamp); + expect(frontElement.layer2Tip).equal(queue[0].layer2Tip); + }); + + it("getSize", async () => { + const size = await priorityQueueTest.getSize(); + expect(size).equal(queue.length); + }); + + it("getFirstUnprocessedPriorityTx", async () => { + const firstUnprocessedTx = await priorityQueueTest.getFirstUnprocessedPriorityTx(); + expect(firstUnprocessedTx).equal(NUMBER_OPERATIONS); + }); + + it("getTotalPriorityTxs", async () => { + const totalPriorityTxs = await priorityQueueTest.getTotalPriorityTxs(); + expect(totalPriorityTxs).equal(queue.length + NUMBER_OPERATIONS); + }); + + it("isEmpty", async () => { + const isEmpty = await priorityQueueTest.isEmpty(); + expect(isEmpty).equal(false); }); + }); }); diff --git a/l1-contracts/test/unit_tests/proxy_test.spec.ts b/l1-contracts/test/unit_tests/proxy_test.spec.ts index a30307d05..076cd7fc9 100644 --- a/l1-contracts/test/unit_tests/proxy_test.spec.ts +++ b/l1-contracts/test/unit_tests/proxy_test.spec.ts @@ -1,171 +1,169 @@ -import { expect } from 'chai'; -import * as ethers from 'ethers'; -import * as hardhat from 'hardhat'; -import { Action, facetCut, diamondCut, getAllSelectors } from '../../src.ts/diamondCut'; +import { expect } from "chai"; +import * as ethers from "ethers"; +import * as hardhat from "hardhat"; +import { Action, facetCut, diamondCut, getAllSelectors } from "../../src.ts/diamondCut"; import type { - DiamondProxy, - DiamondProxyTest, - AdminFacet, - GettersFacet, - MailboxFacet, - DiamondInit -} from '../../typechain'; + DiamondProxy, + DiamondProxyTest, + AdminFacet, + GettersFacet, + MailboxFacet, + DiamondInit, +} from "../../typechain"; import { - DiamondProxyFactory, - DiamondProxyTestFactory, - AdminFacetFactory, - GettersFacetFactory, - MailboxFacetFactory, - DiamondInitFactory, - TestnetERC20TokenFactory -} from '../../typechain'; -import { getCallRevertReason } from './utils'; - -describe('Diamond proxy tests', function () { - let proxy: DiamondProxy; - let diamondInit: DiamondInit; - let adminFacet: AdminFacet; - let gettersFacet: GettersFacet; - let mailboxFacet: MailboxFacet; - let diamondProxyTest: DiamondProxyTest; - let governor: ethers.Signer; - let governorAddress: string; - - before(async () => { - [governor] = await hardhat.ethers.getSigners(); - governorAddress = await governor.getAddress(); - - const diamondInitFactory = await hardhat.ethers.getContractFactory('DiamondInit'); - const diamondInitContract = await diamondInitFactory.deploy(); - diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); - - const adminFactory = await hardhat.ethers.getContractFactory('AdminFacet'); - const adminContract = await adminFactory.deploy(); - adminFacet = AdminFacetFactory.connect(adminContract.address, adminContract.signer); - - const gettersFacetFactory = await hardhat.ethers.getContractFactory('GettersFacet'); - const gettersFacetContract = await gettersFacetFactory.deploy(); - gettersFacet = GettersFacetFactory.connect(gettersFacetContract.address, gettersFacetContract.signer); - - const mailboxFacetFactory = await hardhat.ethers.getContractFactory('MailboxFacet'); - const mailboxFacetContract = await mailboxFacetFactory.deploy(); - mailboxFacet = MailboxFacetFactory.connect(mailboxFacetContract.address, mailboxFacetContract.signer); - - const diamondProxyTestFactory = await hardhat.ethers.getContractFactory('DiamondProxyTest'); - const diamondProxyTestContract = await diamondProxyTestFactory.deploy(); - diamondProxyTest = DiamondProxyTestFactory.connect( - diamondProxyTestContract.address, - diamondProxyTestContract.signer - ); - - const facetCuts = [ - facetCut(adminFacet.address, adminFacet.interface, Action.Add, false), - facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), - facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true) - ]; - - const dummyVerifierParams = { - recursionNodeLevelVkHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionCircuitsSetVksHash: ethers.constants.HashZero - }; - const diamondInitCalldata = diamondInit.interface.encodeFunctionData('initialize', [ - { - verifier: '0x03752D8252d67f99888E741E3fB642803B29B155', - governor: governorAddress, - admin: governorAddress, - genesisBatchHash: '0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21', - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: ethers.constants.HashZero, - allowList: '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55', - verifierParams: dummyVerifierParams, - zkPorterIsAvailable: false, - l2BootloaderBytecodeHash: '0x0100000000000000000000000000000000000000000000000000000000000000', - l2DefaultAccountBytecodeHash: '0x0100000000000000000000000000000000000000000000000000000000000000', - priorityTxMaxGasLimit: 500000, - initialProtocolVersion: 0 - } - ]); - - const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitCalldata); - - const proxyFactory = await hardhat.ethers.getContractFactory('DiamondProxy'); - const chainId = hardhat.network.config.chainId; - const proxyContract = await proxyFactory.deploy(chainId, diamondCutData); - proxy = DiamondProxyFactory.connect(proxyContract.address, proxyContract.signer); - }); - - it('check added selectors', async () => { - const proxyAsGettersFacet = GettersFacetFactory.connect(proxy.address, proxy.signer); - - const dummyFacetSelectors = getAllSelectors(gettersFacet.interface); - for (const selector of dummyFacetSelectors) { - const addr = await proxyAsGettersFacet.facetAddress(selector); - const isFreezable = await proxyAsGettersFacet.isFunctionFreezable(selector); - expect(addr).equal(gettersFacet.address); - expect(isFreezable).equal(false); - } - - const diamondCutSelectors = getAllSelectors(adminFacet.interface); - for (const selector of diamondCutSelectors) { - const addr = await proxyAsGettersFacet.facetAddress(selector); - const isFreezable = await proxyAsGettersFacet.isFunctionFreezable(selector); - expect(addr).equal(adminFacet.address); - expect(isFreezable).equal(false); - } - }); - - it('check that proxy reject non-added selector', async () => { - const proxyAsERC20 = TestnetERC20TokenFactory.connect(proxy.address, proxy.signer); - - const revertReason = await getCallRevertReason(proxyAsERC20.transfer(proxyAsERC20.address, 0)); - expect(revertReason).equal('F'); - }); - - it('check that proxy reject data with no selector', async () => { - const dataWithoutSelector = '0x1122'; - - const revertReason = await getCallRevertReason(proxy.fallback({ data: dataWithoutSelector })); - expect(revertReason).equal('Ut'); - }); - - it('should freeze the diamond storage', async () => { - const proxyAsGettersFacet = GettersFacetFactory.connect(proxy.address, proxy.signer); - - const diamondProxyTestCalldata = diamondProxyTest.interface.encodeFunctionData('setFreezability', [true]); - const diamondCutInitData = diamondCut([], diamondProxyTest.address, diamondProxyTestCalldata); - - const adminFacetExecuteCalldata = adminFacet.interface.encodeFunctionData('executeUpgrade', [ - diamondCutInitData - ]); - await proxy.fallback({ data: adminFacetExecuteCalldata }); - - expect(await proxyAsGettersFacet.isDiamondStorageFrozen()).equal(true); - }); - - it('should not revert on executing a proposal when diamondStorage is frozen', async () => { - const facetCuts = [ - { - facet: adminFacet.address, - selectors: ['0x000000aa'], - action: Action.Add, - isFreezable: false - } - ]; - const diamondCutData = diamondCut(facetCuts, ethers.constants.AddressZero, '0x'); - - const adminFacetExecuteCalldata = adminFacet.interface.encodeFunctionData('executeUpgrade', [diamondCutData]); - await proxy.fallback({ data: adminFacetExecuteCalldata }); - }); - - it('should revert on calling a freezable faucet when diamondStorage is frozen', async () => { - const mailboxFacetSelector0 = getAllSelectors(mailboxFacet.interface)[0]; - const revertReason = await getCallRevertReason(proxy.fallback({ data: mailboxFacetSelector0 })); - expect(revertReason).equal('q1'); - }); - - it('should be able to call an unfreezable faucet when diamondStorage is frozen', async () => { - const gettersFacetSelector1 = getAllSelectors(gettersFacet.interface)[1]; - await proxy.fallback({ data: gettersFacetSelector1 }); - }); + DiamondProxyFactory, + DiamondProxyTestFactory, + AdminFacetFactory, + GettersFacetFactory, + MailboxFacetFactory, + DiamondInitFactory, + TestnetERC20TokenFactory, +} from "../../typechain"; +import { getCallRevertReason } from "./utils"; + +describe("Diamond proxy tests", function () { + let proxy: DiamondProxy; + let diamondInit: DiamondInit; + let adminFacet: AdminFacet; + let gettersFacet: GettersFacet; + let mailboxFacet: MailboxFacet; + let diamondProxyTest: DiamondProxyTest; + let governor: ethers.Signer; + let governorAddress: string; + + before(async () => { + [governor] = await hardhat.ethers.getSigners(); + governorAddress = await governor.getAddress(); + + const diamondInitFactory = await hardhat.ethers.getContractFactory("DiamondInit"); + const diamondInitContract = await diamondInitFactory.deploy(); + diamondInit = DiamondInitFactory.connect(diamondInitContract.address, diamondInitContract.signer); + + const adminFactory = await hardhat.ethers.getContractFactory("AdminFacet"); + const adminContract = await adminFactory.deploy(); + adminFacet = AdminFacetFactory.connect(adminContract.address, adminContract.signer); + + const gettersFacetFactory = await hardhat.ethers.getContractFactory("GettersFacet"); + const gettersFacetContract = await gettersFacetFactory.deploy(); + gettersFacet = GettersFacetFactory.connect(gettersFacetContract.address, gettersFacetContract.signer); + + const mailboxFacetFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); + const mailboxFacetContract = await mailboxFacetFactory.deploy(); + mailboxFacet = MailboxFacetFactory.connect(mailboxFacetContract.address, mailboxFacetContract.signer); + + const diamondProxyTestFactory = await hardhat.ethers.getContractFactory("DiamondProxyTest"); + const diamondProxyTestContract = await diamondProxyTestFactory.deploy(); + diamondProxyTest = DiamondProxyTestFactory.connect( + diamondProxyTestContract.address, + diamondProxyTestContract.signer + ); + + const facetCuts = [ + facetCut(adminFacet.address, adminFacet.interface, Action.Add, false), + facetCut(gettersFacet.address, gettersFacet.interface, Action.Add, false), + facetCut(mailboxFacet.address, mailboxFacet.interface, Action.Add, true), + ]; + + const dummyVerifierParams = { + recursionNodeLevelVkHash: ethers.constants.HashZero, + recursionLeafLevelVkHash: ethers.constants.HashZero, + recursionCircuitsSetVksHash: ethers.constants.HashZero, + }; + const diamondInitCalldata = diamondInit.interface.encodeFunctionData("initialize", [ + { + verifier: "0x03752D8252d67f99888E741E3fB642803B29B155", + governor: governorAddress, + admin: governorAddress, + genesisBatchHash: "0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21", + genesisIndexRepeatedStorageChanges: 0, + genesisBatchCommitment: ethers.constants.HashZero, + allowList: "0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55", + verifierParams: dummyVerifierParams, + zkPorterIsAvailable: false, + l2BootloaderBytecodeHash: "0x0100000000000000000000000000000000000000000000000000000000000000", + l2DefaultAccountBytecodeHash: "0x0100000000000000000000000000000000000000000000000000000000000000", + priorityTxMaxGasLimit: 500000, + initialProtocolVersion: 0, + }, + ]); + + const diamondCutData = diamondCut(facetCuts, diamondInit.address, diamondInitCalldata); + + const proxyFactory = await hardhat.ethers.getContractFactory("DiamondProxy"); + const chainId = hardhat.network.config.chainId; + const proxyContract = await proxyFactory.deploy(chainId, diamondCutData); + proxy = DiamondProxyFactory.connect(proxyContract.address, proxyContract.signer); + }); + + it("check added selectors", async () => { + const proxyAsGettersFacet = GettersFacetFactory.connect(proxy.address, proxy.signer); + + const dummyFacetSelectors = getAllSelectors(gettersFacet.interface); + for (const selector of dummyFacetSelectors) { + const addr = await proxyAsGettersFacet.facetAddress(selector); + const isFreezable = await proxyAsGettersFacet.isFunctionFreezable(selector); + expect(addr).equal(gettersFacet.address); + expect(isFreezable).equal(false); + } + + const diamondCutSelectors = getAllSelectors(adminFacet.interface); + for (const selector of diamondCutSelectors) { + const addr = await proxyAsGettersFacet.facetAddress(selector); + const isFreezable = await proxyAsGettersFacet.isFunctionFreezable(selector); + expect(addr).equal(adminFacet.address); + expect(isFreezable).equal(false); + } + }); + + it("check that proxy reject non-added selector", async () => { + const proxyAsERC20 = TestnetERC20TokenFactory.connect(proxy.address, proxy.signer); + + const revertReason = await getCallRevertReason(proxyAsERC20.transfer(proxyAsERC20.address, 0)); + expect(revertReason).equal("F"); + }); + + it("check that proxy reject data with no selector", async () => { + const dataWithoutSelector = "0x1122"; + + const revertReason = await getCallRevertReason(proxy.fallback({ data: dataWithoutSelector })); + expect(revertReason).equal("Ut"); + }); + + it("should freeze the diamond storage", async () => { + const proxyAsGettersFacet = GettersFacetFactory.connect(proxy.address, proxy.signer); + + const diamondProxyTestCalldata = diamondProxyTest.interface.encodeFunctionData("setFreezability", [true]); + const diamondCutInitData = diamondCut([], diamondProxyTest.address, diamondProxyTestCalldata); + + const adminFacetExecuteCalldata = adminFacet.interface.encodeFunctionData("executeUpgrade", [diamondCutInitData]); + await proxy.fallback({ data: adminFacetExecuteCalldata }); + + expect(await proxyAsGettersFacet.isDiamondStorageFrozen()).equal(true); + }); + + it("should not revert on executing a proposal when diamondStorage is frozen", async () => { + const facetCuts = [ + { + facet: adminFacet.address, + selectors: ["0x000000aa"], + action: Action.Add, + isFreezable: false, + }, + ]; + const diamondCutData = diamondCut(facetCuts, ethers.constants.AddressZero, "0x"); + + const adminFacetExecuteCalldata = adminFacet.interface.encodeFunctionData("executeUpgrade", [diamondCutData]); + await proxy.fallback({ data: adminFacetExecuteCalldata }); + }); + + it("should revert on calling a freezable faucet when diamondStorage is frozen", async () => { + const mailboxFacetSelector0 = getAllSelectors(mailboxFacet.interface)[0]; + const revertReason = await getCallRevertReason(proxy.fallback({ data: mailboxFacetSelector0 })); + expect(revertReason).equal("q1"); + }); + + it("should be able to call an unfreezable faucet when diamondStorage is frozen", async () => { + const gettersFacetSelector1 = getAllSelectors(gettersFacet.interface)[1]; + await proxy.fallback({ data: gettersFacetSelector1 }); + }); }); diff --git a/l1-contracts/test/unit_tests/transaction_validator_test.spec.ts b/l1-contracts/test/unit_tests/transaction_validator_test.spec.ts index 4ec9459f3..a8683c368 100644 --- a/l1-contracts/test/unit_tests/transaction_validator_test.spec.ts +++ b/l1-contracts/test/unit_tests/transaction_validator_test.spec.ts @@ -1,213 +1,213 @@ -import { expect } from 'chai'; -import * as hardhat from 'hardhat'; -import type { TransactionValidatorTest } from '../../typechain'; -import { TransactionValidatorTestFactory } from '../../typechain'; -import { getCallRevertReason } from './utils'; -import * as ethers from 'ethers'; - -describe('TransactionValidator tests', function () { - let tester: TransactionValidatorTest; - before(async () => { - const testerFactory = await hardhat.ethers.getContractFactory('TransactionValidatorTest'); - const testerContract = await testerFactory.deploy(); - tester = TransactionValidatorTestFactory.connect(testerContract.address, testerContract.signer); - }); - - describe('validateL1ToL2Transaction', function () { - it('Should not revert when all parameters are valid', async () => { - await tester.validateL1ToL2Transaction(createTestTransaction({}), 500000); - }); - - it('Should revert when provided gas limit doesnt cover transaction overhead', async () => { - const result = await getCallRevertReason( - tester.validateL1ToL2Transaction( - createTestTransaction({ - gasLimit: 0 - }), - 500000 - ) - ); - expect(result).equal('my'); - }); - - it('Should revert when needed gas is higher than the max', async () => { - const result = await getCallRevertReason(tester.validateL1ToL2Transaction(createTestTransaction({}), 0)); - expect(result).equal('ui'); - }); - - it('Should revert when transaction can output more pubdata than processable', async () => { - const result = await getCallRevertReason( - tester.validateL1ToL2Transaction( - createTestTransaction({ - gasPerPubdataByteLimit: 1 - }), - 500000 - ) - ); - expect(result).equal('uk'); - }); - - it('Should revert when transaction gas doesnt pay the minimum costs', async () => { - const result = await getCallRevertReason( - tester.validateL1ToL2Transaction( - createTestTransaction({ - gasLimit: 200000 - }), - 500000 - ) - ); - expect(result).equal('up'); - }); - }); - - describe('validateUpgradeTransaction', function () { - it('Should not revert when all parameters are valid', async () => { - await tester.validateUpgradeTransaction(createTestTransaction({})); - }); - - it('Should revert when from is too large', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - from: ethers.BigNumber.from(2).pow(16) - }) - ) - ); - expect(result).equal('ua'); - }); - - it('Should revert when to is too large', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - to: ethers.BigNumber.from(2).pow(161) - }) - ) - ); - expect(result).equal('ub'); - }); - - it('Should revert when paymaster is non-zero', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - paymaster: 1 - }) - ) - ); - expect(result).equal('uc'); - }); - - it('Should revert when value is non-zero', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - value: 1 - }) - ) - ); - expect(result).equal('ud'); - }); - - it('Should revert when reserved[0] is non-zero', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [1, 0, 0, 0] - }) - ) - ); - expect(result).equal('ue'); - }); - - it('Should revert when reserved[1] is too large', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [0, ethers.BigNumber.from(2).pow(161), 0, 0] - }) - ) - ); - expect(result).equal('uf'); - }); - - it('Should revert when reserved[2] is non-zero', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [0, 0, 1, 0] - }) - ) - ); - expect(result).equal('ug'); - }); - - it('Should revert when reserved[3] is non-zero', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reserved: [0, 0, 0, 1] - }) - ) - ); - expect(result).equal('uo'); - }); - - it('Should revert when signature has non-zero length', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - signature: '0xaa' - }) - ) - ); - expect(result).equal('uh'); - }); - - it('Should revert when paymaster input has non-zero length', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - paymasterInput: '0xaa' - }) - ) - ); - expect(result).equal('ul'); - }); - - it('Should revert when reserved dynamic field has non-zero length', async () => { - const result = await getCallRevertReason( - tester.validateUpgradeTransaction( - createTestTransaction({ - reservedDynamic: '0xaa' - }) - ) - ); - expect(result).equal('um'); - }); +import { expect } from "chai"; +import * as hardhat from "hardhat"; +import type { TransactionValidatorTest } from "../../typechain"; +import { TransactionValidatorTestFactory } from "../../typechain"; +import { getCallRevertReason } from "./utils"; +import * as ethers from "ethers"; + +describe("TransactionValidator tests", function () { + let tester: TransactionValidatorTest; + before(async () => { + const testerFactory = await hardhat.ethers.getContractFactory("TransactionValidatorTest"); + const testerContract = await testerFactory.deploy(); + tester = TransactionValidatorTestFactory.connect(testerContract.address, testerContract.signer); + }); + + describe("validateL1ToL2Transaction", function () { + it("Should not revert when all parameters are valid", async () => { + await tester.validateL1ToL2Transaction(createTestTransaction({}), 500000); }); + + it("Should revert when provided gas limit doesnt cover transaction overhead", async () => { + const result = await getCallRevertReason( + tester.validateL1ToL2Transaction( + createTestTransaction({ + gasLimit: 0, + }), + 500000 + ) + ); + expect(result).equal("my"); + }); + + it("Should revert when needed gas is higher than the max", async () => { + const result = await getCallRevertReason(tester.validateL1ToL2Transaction(createTestTransaction({}), 0)); + expect(result).equal("ui"); + }); + + it("Should revert when transaction can output more pubdata than processable", async () => { + const result = await getCallRevertReason( + tester.validateL1ToL2Transaction( + createTestTransaction({ + gasPerPubdataByteLimit: 1, + }), + 500000 + ) + ); + expect(result).equal("uk"); + }); + + it("Should revert when transaction gas doesnt pay the minimum costs", async () => { + const result = await getCallRevertReason( + tester.validateL1ToL2Transaction( + createTestTransaction({ + gasLimit: 200000, + }), + 500000 + ) + ); + expect(result).equal("up"); + }); + }); + + describe("validateUpgradeTransaction", function () { + it("Should not revert when all parameters are valid", async () => { + await tester.validateUpgradeTransaction(createTestTransaction({})); + }); + + it("Should revert when from is too large", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + from: ethers.BigNumber.from(2).pow(16), + }) + ) + ); + expect(result).equal("ua"); + }); + + it("Should revert when to is too large", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + to: ethers.BigNumber.from(2).pow(161), + }) + ) + ); + expect(result).equal("ub"); + }); + + it("Should revert when paymaster is non-zero", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + paymaster: 1, + }) + ) + ); + expect(result).equal("uc"); + }); + + it("Should revert when value is non-zero", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + value: 1, + }) + ) + ); + expect(result).equal("ud"); + }); + + it("Should revert when reserved[0] is non-zero", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + reserved: [1, 0, 0, 0], + }) + ) + ); + expect(result).equal("ue"); + }); + + it("Should revert when reserved[1] is too large", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + reserved: [0, ethers.BigNumber.from(2).pow(161), 0, 0], + }) + ) + ); + expect(result).equal("uf"); + }); + + it("Should revert when reserved[2] is non-zero", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + reserved: [0, 0, 1, 0], + }) + ) + ); + expect(result).equal("ug"); + }); + + it("Should revert when reserved[3] is non-zero", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + reserved: [0, 0, 0, 1], + }) + ) + ); + expect(result).equal("uo"); + }); + + it("Should revert when signature has non-zero length", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + signature: "0xaa", + }) + ) + ); + expect(result).equal("uh"); + }); + + it("Should revert when paymaster input has non-zero length", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + paymasterInput: "0xaa", + }) + ) + ); + expect(result).equal("ul"); + }); + + it("Should revert when reserved dynamic field has non-zero length", async () => { + const result = await getCallRevertReason( + tester.validateUpgradeTransaction( + createTestTransaction({ + reservedDynamic: "0xaa", + }) + ) + ); + expect(result).equal("um"); + }); + }); }); function createTestTransaction(overrides) { - return Object.assign( - { - txType: 0, - from: ethers.BigNumber.from(2).pow(16).sub(1), - to: 0, - gasLimit: 500000, - gasPerPubdataByteLimit: 800, - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - paymaster: 0, - nonce: 0, - value: 0, - reserved: [0, 0, 0, 0], - data: '0x', - signature: '0x', - factoryDeps: [], - paymasterInput: '0x', - reservedDynamic: '0x' - }, - overrides - ); + return Object.assign( + { + txType: 0, + from: ethers.BigNumber.from(2).pow(16).sub(1), + to: 0, + gasLimit: 500000, + gasPerPubdataByteLimit: 800, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymaster: 0, + nonce: 0, + value: 0, + reserved: [0, 0, 0, 0], + data: "0x", + signature: "0x", + factoryDeps: [], + paymasterInput: "0x", + reservedDynamic: "0x", + }, + overrides + ); } diff --git a/l1-contracts/test/unit_tests/utils.ts b/l1-contracts/test/unit_tests/utils.ts index 449cab44e..0ba362c60 100644 --- a/l1-contracts/test/unit_tests/utils.ts +++ b/l1-contracts/test/unit_tests/utils.ts @@ -1,179 +1,173 @@ -import type { BigNumberish, BytesLike } from 'ethers'; -import { BigNumber, ethers } from 'ethers'; -import type { Address } from 'zksync-web3/build/src/types'; +import type { BigNumberish, BytesLike } from "ethers"; +import { BigNumber, ethers } from "ethers"; +import type { Address } from "zksync-web3/build/src/types"; // eslint-disable-next-line @typescript-eslint/no-var-requires -export const IERC20_INTERFACE = require('@openzeppelin/contracts/build/contracts/IERC20'); -export const DEFAULT_REVERT_REASON = 'VM did not revert'; +export const IERC20_INTERFACE = require("@openzeppelin/contracts/build/contracts/IERC20"); +export const DEFAULT_REVERT_REASON = "VM did not revert"; -export const EMPTY_STRING_KECCAK = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; -export const DEFAULT_L2_LOGS_TREE_ROOT_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000'; -export const L2_SYSTEM_CONTEXT_ADDRESS = '0x000000000000000000000000000000000000800b'; -export const L2_BOOTLOADER_ADDRESS = '0x0000000000000000000000000000000000008001'; -export const L2_KNOWN_CODE_STORAGE_ADDRESS = '0x0000000000000000000000000000000000008004'; -export const L2_TO_L1_MESSENGER = '0x0000000000000000000000000000000000008008'; -export const L2_BYTECODE_COMPRESSOR_ADDRESS = '0x000000000000000000000000000000000000800e'; +export const EMPTY_STRING_KECCAK = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; +export const DEFAULT_L2_LOGS_TREE_ROOT_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; +export const L2_SYSTEM_CONTEXT_ADDRESS = "0x000000000000000000000000000000000000800b"; +export const L2_BOOTLOADER_ADDRESS = "0x0000000000000000000000000000000000008001"; +export const L2_KNOWN_CODE_STORAGE_ADDRESS = "0x0000000000000000000000000000000000008004"; +export const L2_TO_L1_MESSENGER = "0x0000000000000000000000000000000000008008"; +export const L2_BYTECODE_COMPRESSOR_ADDRESS = "0x000000000000000000000000000000000000800e"; export enum SYSTEM_LOG_KEYS { - L2_TO_L1_LOGS_TREE_ROOT_KEY, - TOTAL_L2_TO_L1_PUBDATA_KEY, - STATE_DIFF_HASH_KEY, - PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - PREV_BATCH_HASH_KEY, - CHAINED_PRIORITY_TXN_HASH_KEY, - NUMBER_OF_LAYER_1_TXS_KEY, - EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY + L2_TO_L1_LOGS_TREE_ROOT_KEY, + TOTAL_L2_TO_L1_PUBDATA_KEY, + STATE_DIFF_HASH_KEY, + PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, + PREV_BATCH_HASH_KEY, + CHAINED_PRIORITY_TXN_HASH_KEY, + NUMBER_OF_LAYER_1_TXS_KEY, + EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY, } // The default price for the pubdata in L2 gas to be used in L1->L2 transactions export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = - // eslint-disable-next-line @typescript-eslint/no-var-requires - require('../../../SystemConfig.json').REQUIRED_L2_GAS_PRICE_PER_PUBDATA; + // eslint-disable-next-line @typescript-eslint/no-var-requires + require("../../../SystemConfig.json").REQUIRED_L2_GAS_PRICE_PER_PUBDATA; /// Set of parameters that are needed to test the processing of priority operations export class DummyOp { - constructor(public id: number, public expirationBatch: BigNumber, public layer2Tip: number) {} + constructor( + public id: number, + public expirationBatch: BigNumber, + public layer2Tip: number + ) {} } export enum AccessMode { - Closed = 0, - SpecialAccessOnly = 1, - Public = 2 + Closed = 0, + SpecialAccessOnly = 1, + Public = 2, } export async function getCallRevertReason(promise) { - let revertReason = DEFAULT_REVERT_REASON; + let revertReason = DEFAULT_REVERT_REASON; + try { + await promise; + } catch (e) { try { - await promise; - } catch (e) { - try { - revertReason = e.reason.match(/reverted with reason string '(.*)'/)?.[1] || e.reason; - } catch (_) { - throw e; - } + revertReason = e.reason.match(/reverted with reason string '(.*)'/)?.[1] || e.reason; + } catch (_) { + throw e; } - return revertReason; + } + return revertReason; } export async function requestExecute( - mailbox: ethers.Contract, - to: Address, - l2Value: ethers.BigNumber, - calldata: ethers.BytesLike, - l2GasLimit: ethers.BigNumber, - factoryDeps: BytesLike[], - refundRecipient: string, - overrides?: ethers.PayableOverrides + mailbox: ethers.Contract, + to: Address, + l2Value: ethers.BigNumber, + calldata: ethers.BytesLike, + l2GasLimit: ethers.BigNumber, + factoryDeps: BytesLike[], + refundRecipient: string, + overrides?: ethers.PayableOverrides ) { - overrides ??= {}; - overrides.gasPrice ??= mailbox.provider.getGasPrice(); - - if (!overrides.value) { - const baseCost = await mailbox.l2TransactionBaseCost( - overrides.gasPrice, - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA - ); - overrides.value = baseCost.add(l2Value); - } - - return await mailbox.requestL2Transaction( - to, - l2Value, - calldata, - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - factoryDeps, - refundRecipient, - overrides + overrides ??= {}; + overrides.gasPrice ??= mailbox.provider.getGasPrice(); + + if (!overrides.value) { + const baseCost = await mailbox.l2TransactionBaseCost( + overrides.gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA ); + overrides.value = baseCost.add(l2Value); + } + + return await mailbox.requestL2Transaction( + to, + l2Value, + calldata, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps, + refundRecipient, + overrides + ); } export function constructL2Log(isService: boolean, sender: string, key: number | string, value: string) { - return ethers.utils.hexConcat([ - isService ? '0x0001' : '0x0000', - '0x0000', - sender, - ethers.utils.hexZeroPad(ethers.utils.hexlify(key), 32), - ethers.utils.hexZeroPad(ethers.utils.hexlify(value), 32) - ]); + return ethers.utils.hexConcat([ + isService ? "0x0001" : "0x0000", + "0x0000", + sender, + ethers.utils.hexZeroPad(ethers.utils.hexlify(key), 32), + ethers.utils.hexZeroPad(ethers.utils.hexlify(value), 32), + ]); } export function createSystemLogs() { - return [ - constructL2Log( - true, - L2_TO_L1_MESSENGER, - SYSTEM_LOG_KEYS.L2_TO_L1_LOGS_TREE_ROOT_KEY, - ethers.constants.HashZero - ), - constructL2Log( - true, - L2_TO_L1_MESSENGER, - SYSTEM_LOG_KEYS.TOTAL_L2_TO_L1_PUBDATA_KEY, - '0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563' - ), - constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.STATE_DIFF_HASH_KEY, ethers.constants.HashZero), - constructL2Log( - true, - L2_SYSTEM_CONTEXT_ADDRESS, - SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, - ethers.constants.HashZero - ), - constructL2Log(true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, ethers.constants.HashZero), - constructL2Log(true, L2_BOOTLOADER_ADDRESS, SYSTEM_LOG_KEYS.CHAINED_PRIORITY_TXN_HASH_KEY, EMPTY_STRING_KECCAK), - constructL2Log( - true, - L2_BOOTLOADER_ADDRESS, - SYSTEM_LOG_KEYS.NUMBER_OF_LAYER_1_TXS_KEY, - ethers.constants.HashZero - ) - ]; + return [ + constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.L2_TO_L1_LOGS_TREE_ROOT_KEY, ethers.constants.HashZero), + constructL2Log( + true, + L2_TO_L1_MESSENGER, + SYSTEM_LOG_KEYS.TOTAL_L2_TO_L1_PUBDATA_KEY, + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563" + ), + constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.STATE_DIFF_HASH_KEY, ethers.constants.HashZero), + constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, + ethers.constants.HashZero + ), + constructL2Log(true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, ethers.constants.HashZero), + constructL2Log(true, L2_BOOTLOADER_ADDRESS, SYSTEM_LOG_KEYS.CHAINED_PRIORITY_TXN_HASH_KEY, EMPTY_STRING_KECCAK), + constructL2Log(true, L2_BOOTLOADER_ADDRESS, SYSTEM_LOG_KEYS.NUMBER_OF_LAYER_1_TXS_KEY, ethers.constants.HashZero), + ]; } export function genesisStoredBatchInfo(): StoredBatchInfo { - return { - batchNumber: 0, - batchHash: ethers.constants.HashZero, - indexRepeatedStorageChanges: 0, - numberOfLayer1Txs: 0, - priorityOperationsHash: EMPTY_STRING_KECCAK, - l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, - timestamp: 0, - commitment: ethers.constants.HashZero - }; + return { + batchNumber: 0, + batchHash: ethers.constants.HashZero, + indexRepeatedStorageChanges: 0, + numberOfLayer1Txs: 0, + priorityOperationsHash: EMPTY_STRING_KECCAK, + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: 0, + commitment: ethers.constants.HashZero, + }; } // Packs the batch timestamp and L2 block timestamp and returns the 32-byte hex string // which should be used for the "key" field of the L2->L1 system context log. export function packBatchTimestampAndBatchTimestamp( - batchTimestamp: BigNumberish, - l2BlockTimestamp: BigNumberish + batchTimestamp: BigNumberish, + l2BlockTimestamp: BigNumberish ): string { - const packedNum = BigNumber.from(batchTimestamp).shl(128).or(BigNumber.from(l2BlockTimestamp)); - return ethers.utils.hexZeroPad(ethers.utils.hexlify(packedNum), 32); + const packedNum = BigNumber.from(batchTimestamp).shl(128).or(BigNumber.from(l2BlockTimestamp)); + return ethers.utils.hexZeroPad(ethers.utils.hexlify(packedNum), 32); } export interface StoredBatchInfo { - batchNumber: BigNumberish; - batchHash: BytesLike; - indexRepeatedStorageChanges: BigNumberish; - numberOfLayer1Txs: BigNumberish; - priorityOperationsHash: BytesLike; - l2LogsTreeRoot: BytesLike; - timestamp: BigNumberish; - commitment: BytesLike; + batchNumber: BigNumberish; + batchHash: BytesLike; + indexRepeatedStorageChanges: BigNumberish; + numberOfLayer1Txs: BigNumberish; + priorityOperationsHash: BytesLike; + l2LogsTreeRoot: BytesLike; + timestamp: BigNumberish; + commitment: BytesLike; } export interface CommitBatchInfo { - batchNumber: BigNumberish; - timestamp: number; - indexRepeatedStorageChanges: BigNumberish; - newStateRoot: BytesLike; - numberOfLayer1Txs: BigNumberish; - priorityOperationsHash: BytesLike; - bootloaderHeapInitialContentsHash: BytesLike; - eventsQueueStateHash: BytesLike; - systemLogs: BytesLike; - totalL2ToL1Pubdata: BytesLike; + batchNumber: BigNumberish; + timestamp: number; + indexRepeatedStorageChanges: BigNumberish; + newStateRoot: BytesLike; + numberOfLayer1Txs: BigNumberish; + priorityOperationsHash: BytesLike; + bootloaderHeapInitialContentsHash: BytesLike; + eventsQueueStateHash: BytesLike; + systemLogs: BytesLike; + totalL2ToL1Pubdata: BytesLike; } diff --git a/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts b/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts index 3a1aea4fc..870a18651 100644 --- a/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts +++ b/l1-contracts/test/unit_tests/validator_timelock_test.spec.ts @@ -1,255 +1,249 @@ -import { expect } from 'chai'; -import { ethers } from 'ethers'; -import * as hardhat from 'hardhat'; -import type { DummyExecutor, ValidatorTimelock } from '../../typechain'; -import { DummyExecutorFactory, ValidatorTimelockFactory } from '../../typechain'; -import { getCallRevertReason } from './utils'; - -describe('ValidatorTimelock tests', function () { - let owner: ethers.Signer; - let validator: ethers.Signer; - let randomSigner: ethers.Signer; - let validatorTimelock: ValidatorTimelock; - let dummyExecutor: DummyExecutor; - - const MOCK_PROOF_INPUT = { - recursiveAggregationInput: [], - serializedProof: [] +import { expect } from "chai"; +import { ethers } from "ethers"; +import * as hardhat from "hardhat"; +import type { DummyExecutor, ValidatorTimelock } from "../../typechain"; +import { DummyExecutorFactory, ValidatorTimelockFactory } from "../../typechain"; +import { getCallRevertReason } from "./utils"; + +describe("ValidatorTimelock tests", function () { + let owner: ethers.Signer; + let validator: ethers.Signer; + let randomSigner: ethers.Signer; + let validatorTimelock: ValidatorTimelock; + let dummyExecutor: DummyExecutor; + + const MOCK_PROOF_INPUT = { + recursiveAggregationInput: [], + serializedProof: [], + }; + + function getMockCommitBatchInfo(batchNumber: number, timestamp: number = 0) { + return { + batchNumber, + timestamp, + indexRepeatedStorageChanges: 0, + newStateRoot: ethers.constants.HashZero, + numberOfLayer1Txs: 0, + priorityOperationsHash: ethers.constants.HashZero, + bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), + eventsQueueStateHash: ethers.utils.randomBytes(32), + systemLogs: [], + totalL2ToL1Pubdata: "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", }; + } + + function getMockStoredBatchInfo(batchNumber: number, timestamp: number = 0) { + return { + batchNumber, + batchHash: ethers.constants.HashZero, + indexRepeatedStorageChanges: 0, + numberOfLayer1Txs: 0, + priorityOperationsHash: ethers.constants.HashZero, + l2LogsTreeRoot: ethers.constants.HashZero, + timestamp, + commitment: ethers.constants.HashZero, + }; + } - function getMockCommitBatchInfo(batchNumber: number, timestamp: number = 0) { - return { - batchNumber, - timestamp, - indexRepeatedStorageChanges: 0, - newStateRoot: ethers.constants.HashZero, - numberOfLayer1Txs: 0, - priorityOperationsHash: ethers.constants.HashZero, - bootloaderHeapInitialContentsHash: ethers.utils.randomBytes(32), - eventsQueueStateHash: ethers.utils.randomBytes(32), - systemLogs: [], - totalL2ToL1Pubdata: '0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563' - }; - } + before(async () => { + [owner, validator, randomSigner] = await hardhat.ethers.getSigners(); - function getMockStoredBatchInfo(batchNumber: number, timestamp: number = 0) { - return { - batchNumber, - batchHash: ethers.constants.HashZero, - indexRepeatedStorageChanges: 0, - numberOfLayer1Txs: 0, - priorityOperationsHash: ethers.constants.HashZero, - l2LogsTreeRoot: ethers.constants.HashZero, - timestamp, - commitment: ethers.constants.HashZero - }; - } + const dummyExecutorFactory = await hardhat.ethers.getContractFactory("DummyExecutor"); + const dummyExecutorContract = await dummyExecutorFactory.deploy(); + dummyExecutor = DummyExecutorFactory.connect(dummyExecutorContract.address, dummyExecutorContract.signer); + + const validatorTimelockFactory = await hardhat.ethers.getContractFactory("ValidatorTimelock"); + const validatorTimelockContract = await validatorTimelockFactory.deploy( + await owner.getAddress(), + dummyExecutor.address, + 0, + ethers.constants.AddressZero + ); + validatorTimelock = ValidatorTimelockFactory.connect( + validatorTimelockContract.address, + validatorTimelockContract.signer + ); + }); + + it("Should revert if non-validator commits batches", async () => { + const revertReason = await getCallRevertReason( + validatorTimelock.connect(randomSigner).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]) + ); + + expect(revertReason).equal("8h"); + }); + + it("Should revert if non-validator proves batches", async () => { + const revertReason = await getCallRevertReason( + validatorTimelock + .connect(randomSigner) + .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT) + ); + + expect(revertReason).equal("8h"); + }); + + it("Should revert if non-validator revert batches", async () => { + const revertReason = await getCallRevertReason(validatorTimelock.connect(randomSigner).revertBatches(1)); + + expect(revertReason).equal("8h"); + }); - before(async () => { - [owner, validator, randomSigner] = await hardhat.ethers.getSigners(); - - const dummyExecutorFactory = await hardhat.ethers.getContractFactory('DummyExecutor'); - const dummyExecutorContract = await dummyExecutorFactory.deploy(); - dummyExecutor = DummyExecutorFactory.connect(dummyExecutorContract.address, dummyExecutorContract.signer); - - const validatorTimelockFactory = await hardhat.ethers.getContractFactory('ValidatorTimelock'); - const validatorTimelockContract = await validatorTimelockFactory.deploy( - await owner.getAddress(), - dummyExecutor.address, - 0, - ethers.constants.AddressZero - ); - validatorTimelock = ValidatorTimelockFactory.connect( - validatorTimelockContract.address, - validatorTimelockContract.signer - ); - }); + it("Should revert if non-validator executes batches", async () => { + const revertReason = await getCallRevertReason( + validatorTimelock.connect(randomSigner).executeBatches([getMockStoredBatchInfo(1)]) + ); - it('Should revert if non-validator commits batches', async () => { - const revertReason = await getCallRevertReason( - validatorTimelock - .connect(randomSigner) - .commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]) - ); + expect(revertReason).equal("8h"); + }); - expect(revertReason).equal('8h'); - }); + it("Should revert if non-owner sets validator", async () => { + const revertReason = await getCallRevertReason( + validatorTimelock.connect(randomSigner).setValidator(await randomSigner.getAddress()) + ); - it('Should revert if non-validator proves batches', async () => { - const revertReason = await getCallRevertReason( - validatorTimelock - .connect(randomSigner) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT) - ); + expect(revertReason).equal("Ownable: caller is not the owner"); + }); - expect(revertReason).equal('8h'); - }); + it("Should revert if non-owner sets execution delay", async () => { + const revertReason = await getCallRevertReason(validatorTimelock.connect(randomSigner).setExecutionDelay(1000)); - it('Should revert if non-validator revert batches', async () => { - const revertReason = await getCallRevertReason(validatorTimelock.connect(randomSigner).revertBatches(1)); + expect(revertReason).equal("Ownable: caller is not the owner"); + }); + + it("Should successfully set the validator", async () => { + const validatorAddress = await validator.getAddress(); + await validatorTimelock.connect(owner).setValidator(validatorAddress); + + expect(await validatorTimelock.validator()).equal(validatorAddress); + }); + + it("Should successfully set the execution delay", async () => { + await validatorTimelock.connect(owner).setExecutionDelay(10); // set to 10 seconds + + expect(await validatorTimelock.executionDelay()).equal(10); + }); + + it("Should successfully commit batches", async () => { + await validatorTimelock.connect(validator).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); + + expect(await dummyExecutor.getTotalBatchesCommitted()).equal(1); + }); + + it("Should successfully prove batches", async () => { + await validatorTimelock + .connect(validator) + .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1, 1)], MOCK_PROOF_INPUT); + + expect(await dummyExecutor.getTotalBatchesVerified()).equal(1); + }); + + it("Should revert on executing earlier than the delay", async () => { + const revertReason = await getCallRevertReason( + validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(1)]) + ); + + expect(revertReason).equal("5c"); + }); + + it("Should successfully revert batches", async () => { + await validatorTimelock.connect(validator).revertBatches(0); + + expect(await dummyExecutor.getTotalBatchesVerified()).equal(0); + expect(await dummyExecutor.getTotalBatchesCommitted()).equal(0); + }); + + it("Should successfully overwrite the committing timestamp on the reverted batches timestamp", async () => { + const revertedBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(1)); + + await validatorTimelock.connect(validator).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); + + await validatorTimelock + .connect(validator) + .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT); - expect(revertReason).equal('8h'); - }); + const newBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(1)); - it('Should revert if non-validator executes batches', async () => { - const revertReason = await getCallRevertReason( - validatorTimelock.connect(randomSigner).executeBatches([getMockStoredBatchInfo(1)]) - ); - - expect(revertReason).equal('8h'); - }); + expect(newBatchesTimestamp).greaterThanOrEqual(revertedBatchesTimestamp); + }); - it('Should revert if non-owner sets validator', async () => { - const revertReason = await getCallRevertReason( - validatorTimelock.connect(randomSigner).setValidator(await randomSigner.getAddress()) - ); - - expect(revertReason).equal('Ownable: caller is not the owner'); - }); - - it('Should revert if non-owner sets execution delay', async () => { - const revertReason = await getCallRevertReason(validatorTimelock.connect(randomSigner).setExecutionDelay(1000)); + it("Should successfully execute batches after the delay", async () => { + await hardhat.network.provider.send("hardhat_mine", ["0x2", "0xc"]); //mine 2 batches with intervals of 12 seconds + await validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(1)]); + expect(await dummyExecutor.getTotalBatchesExecuted()).equal(1); + }); - expect(revertReason).equal('Ownable: caller is not the owner'); - }); - - it('Should successfully set the validator', async () => { - const validatorAddress = await validator.getAddress(); - await validatorTimelock.connect(owner).setValidator(validatorAddress); - - expect(await validatorTimelock.validator()).equal(validatorAddress); - }); - - it('Should successfully set the execution delay', async () => { - await validatorTimelock.connect(owner).setExecutionDelay(10); // set to 10 seconds - - expect(await validatorTimelock.executionDelay()).equal(10); - }); - - it('Should successfully commit batches', async () => { - await validatorTimelock - .connect(validator) - .commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); - - expect(await dummyExecutor.getTotalBatchesCommitted()).equal(1); - }); - - it('Should successfully prove batches', async () => { - await validatorTimelock - .connect(validator) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1, 1)], MOCK_PROOF_INPUT); - - expect(await dummyExecutor.getTotalBatchesVerified()).equal(1); - }); - - it('Should revert on executing earlier than the delay', async () => { - const revertReason = await getCallRevertReason( - validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(1)]) - ); - - expect(revertReason).equal('5c'); - }); - - it('Should successfully revert batches', async () => { - await validatorTimelock.connect(validator).revertBatches(0); - - expect(await dummyExecutor.getTotalBatchesVerified()).equal(0); - expect(await dummyExecutor.getTotalBatchesCommitted()).equal(0); - }); - - it('Should successfully overwrite the committing timestamp on the reverted batches timestamp', async () => { - const revertedBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(1)); - - await validatorTimelock - .connect(validator) - .commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(1)]); - - await validatorTimelock - .connect(validator) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(1)], MOCK_PROOF_INPUT); - - const newBatchesTimestamp = Number(await validatorTimelock.getCommittedBatchTimestamp(1)); - - expect(newBatchesTimestamp).greaterThanOrEqual(revertedBatchesTimestamp); - }); - - it('Should successfully execute batches after the delay', async () => { - await hardhat.network.provider.send('hardhat_mine', ['0x2', '0xc']); //mine 2 batches with intervals of 12 seconds - await validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(1)]); - expect(await dummyExecutor.getTotalBatchesExecuted()).equal(1); - }); - - it('Should revert if validator tries to commit batches with invalid last committed batchNumber', async () => { - const revertReason = await getCallRevertReason( - validatorTimelock.connect(validator).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(2)]) - ); - - // Error should be forwarded from the DummyExecutor - expect(revertReason).equal('DummyExecutor: Invalid last committed batch number'); - }); - - // Test case to check if proving batches with invalid batchNumber fails - it('Should revert if validator tries to prove batches with invalid batchNumber', async () => { - const revertReason = await getCallRevertReason( - validatorTimelock - .connect(validator) - .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(2, 1)], MOCK_PROOF_INPUT) - ); - - expect(revertReason).equal('DummyExecutor: Invalid previous batch number'); - }); - - it('Should revert if validator tries to execute more batches than were proven', async () => { - await hardhat.network.provider.send('hardhat_mine', ['0x2', '0xc']); //mine 2 batches with intervals of 12 seconds - const revertReason = await getCallRevertReason( - validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(2)]) - ); - - expect(revertReason).equal("DummyExecutor: Can't execute batches more than committed and proven currently"); - }); - - // These tests primarily needed to make gas statistics be more accurate. - - it('Should commit multiple batches in one transaction', async () => { - await validatorTimelock - .connect(validator) - .commitBatches(getMockStoredBatchInfo(1), [ - getMockCommitBatchInfo(2), - getMockCommitBatchInfo(3), - getMockCommitBatchInfo(4), - getMockCommitBatchInfo(5), - getMockCommitBatchInfo(6), - getMockCommitBatchInfo(7), - getMockCommitBatchInfo(8) - ]); - - expect(await dummyExecutor.getTotalBatchesCommitted()).equal(8); - }); - - it('Should prove multiple batches in one transactions', async () => { - for (let i = 1; i < 8; i++) { - await validatorTimelock - .connect(validator) - .proveBatches(getMockStoredBatchInfo(i), [getMockStoredBatchInfo(i + 1)], MOCK_PROOF_INPUT); - - expect(await dummyExecutor.getTotalBatchesVerified()).equal(i + 1); - } - }); - - it('Should execute multiple batches in multiple transactions', async () => { - await hardhat.network.provider.send('hardhat_mine', ['0x2', '0xc']); //mine 2 batches with intervals of 12 seconds - await validatorTimelock - .connect(validator) - .executeBatches([ - getMockStoredBatchInfo(2), - getMockStoredBatchInfo(3), - getMockStoredBatchInfo(4), - getMockStoredBatchInfo(5), - getMockStoredBatchInfo(6), - getMockStoredBatchInfo(7), - getMockStoredBatchInfo(8) - ]); - - expect(await dummyExecutor.getTotalBatchesExecuted()).equal(8); - }); + it("Should revert if validator tries to commit batches with invalid last committed batchNumber", async () => { + const revertReason = await getCallRevertReason( + validatorTimelock.connect(validator).commitBatches(getMockStoredBatchInfo(0), [getMockCommitBatchInfo(2)]) + ); + + // Error should be forwarded from the DummyExecutor + expect(revertReason).equal("DummyExecutor: Invalid last committed batch number"); + }); + + // Test case to check if proving batches with invalid batchNumber fails + it("Should revert if validator tries to prove batches with invalid batchNumber", async () => { + const revertReason = await getCallRevertReason( + validatorTimelock + .connect(validator) + .proveBatches(getMockStoredBatchInfo(0), [getMockStoredBatchInfo(2, 1)], MOCK_PROOF_INPUT) + ); + + expect(revertReason).equal("DummyExecutor: Invalid previous batch number"); + }); + + it("Should revert if validator tries to execute more batches than were proven", async () => { + await hardhat.network.provider.send("hardhat_mine", ["0x2", "0xc"]); //mine 2 batches with intervals of 12 seconds + const revertReason = await getCallRevertReason( + validatorTimelock.connect(validator).executeBatches([getMockStoredBatchInfo(2)]) + ); + + expect(revertReason).equal("DummyExecutor: Can't execute batches more than committed and proven currently"); + }); + + // These tests primarily needed to make gas statistics be more accurate. + + it("Should commit multiple batches in one transaction", async () => { + await validatorTimelock + .connect(validator) + .commitBatches(getMockStoredBatchInfo(1), [ + getMockCommitBatchInfo(2), + getMockCommitBatchInfo(3), + getMockCommitBatchInfo(4), + getMockCommitBatchInfo(5), + getMockCommitBatchInfo(6), + getMockCommitBatchInfo(7), + getMockCommitBatchInfo(8), + ]); + + expect(await dummyExecutor.getTotalBatchesCommitted()).equal(8); + }); + + it("Should prove multiple batches in one transactions", async () => { + for (let i = 1; i < 8; i++) { + await validatorTimelock + .connect(validator) + .proveBatches(getMockStoredBatchInfo(i), [getMockStoredBatchInfo(i + 1)], MOCK_PROOF_INPUT); + + expect(await dummyExecutor.getTotalBatchesVerified()).equal(i + 1); + } + }); + + it("Should execute multiple batches in multiple transactions", async () => { + await hardhat.network.provider.send("hardhat_mine", ["0x2", "0xc"]); //mine 2 batches with intervals of 12 seconds + await validatorTimelock + .connect(validator) + .executeBatches([ + getMockStoredBatchInfo(2), + getMockStoredBatchInfo(3), + getMockStoredBatchInfo(4), + getMockStoredBatchInfo(5), + getMockStoredBatchInfo(6), + getMockStoredBatchInfo(7), + getMockStoredBatchInfo(8), + ]); + + expect(await dummyExecutor.getTotalBatchesExecuted()).equal(8); + }); }); diff --git a/l1-contracts/test/unit_tests/verifier.spec.ts b/l1-contracts/test/unit_tests/verifier.spec.ts index 9352ec9ef..437c168b9 100644 --- a/l1-contracts/test/unit_tests/verifier.spec.ts +++ b/l1-contracts/test/unit_tests/verifier.spec.ts @@ -1,439 +1,397 @@ -import * as hardhat from 'hardhat'; -import { expect } from 'chai'; -import type { VerifierTest, VerifierRecursiveTest } from '../../typechain'; -import { VerifierTestFactory } from '../../typechain'; -import { getCallRevertReason } from './utils'; -import { ethers } from 'hardhat'; - -describe('Verifier test', function () { - const Q_MOD = '21888242871839275222246405745257275088696311157297823662689037894645226208583'; - const R_MOD = '21888242871839275222246405745257275088548364400416034343698204186575808495617'; - - const PROOF = { - publicInputs: ['0xa3dd954bb76c1474c1a04f04870cc75bcaf66ec23c0303c87fb119f9'], - serializedProof: [ - '0x162e0e35310fa1265df0051490fad590e875a98b4e7781ce1bb2698887e24070', - '0x1a3645718b688a382a00b99059f9488daf624d04ceb39b5553f0a1a0d508dde6', - '0x44df31be22763cde0700cc784f70758b944096a11c9b32bfb4f559d9b6a9567', - '0x2efae700419dd3fa0bebf5404efef2f3b5f8f2288c595ec219a05607e9971c9', - '0x223e7327348fd30effc617ee9fa7e28117869f149719cf93c20788cb78adc291', - '0x99f67d073880787c73d54bc2509c1611ac6f48fbe3b5214b4dc2f3cb3a572c0', - '0x17365bde1bbcd62561764ddd8b2d562edbe1c07519cd23f03831b694c6665a2d', - '0x2f321ac8e18ab998f8fe370f3b5114598881798ccc6eac24d7f4161c15fdabb3', - '0x2f6b4b0f4973f2f6e2fa5ecd34602b20b56f0e4fb551b011af96e555fdc1197d', - '0xb8d070fec07e8467425605015acba755f54db7f566c6704818408d927419d80', - '0x103185cff27eef6e8090373749a8065129fcc93482bd6ea4db1808725b6da2e', - '0x29b35d35c22deda2ac9dd56a9f6a145871b1b6557e165296f804297160d5f98b', - '0x240bb4b0b7e30e71e8af2d908e72bf47b6496aab1e1f7cb32f2604d79f76cff8', - '0x1cd2156a0f0c1944a8a3359618ff978b27eb42075c667960817be624ce161489', - '0xbd0b75112591ab1b4a6a3e03fb76368419b78e4b95ee773b8ef5e7848695cf7', - '0xcd1da7fcfc27d2d9e9743e80951694995b162298d4109428fcf1c9a90f24905', - '0x2672327da3fdec6c58e8a0d33ca94e059da0787e9221a2a0ac412692cc962aac', - '0x50e88db23f7582691a0fb7e5c95dd713e54188833fe1d241e3e32a98dfeb0f0', - '0x8dc78ede51774238b0984b02ac7fcf8b0a8dfcb6ca733b90c6b44aac4551057', - '0x2a3167374e2d54e47ce865ef222346adf7a27d4174820a637cf656899238387', - '0x2f161fddcebb9ed8740c14d3a782efcf6f0ad069371194f87bcc04f9e9baf2ee', - '0x25dcf81d1721eab45e86ccfee579eaa4e54a4a80a19edf784f24cc1ee831e58a', - '0x1e483708e664ced677568d93b3b4f505e9d2968f802e04b31873f7d8f635fb0f', - '0x2bf6cdf920d353ba8bda932b72bf6ff6a93aa831274a5dc3ea6ea647a446d18e', - '0x2aa406a77d9143221165e066adfcc9281b9c90afdcee4336eda87f85d2bfe5b', - '0x26fc05b152609664e624a233e52e12252a0cae9d2a86a36717300063faca4b4b', - '0x24579fb180a63e5594644f4726c5af6d091aee4ee64c2c2a37d98f646a9c8d9d', - '0xb34ff9cbae3a9afe40e80a46e7d1419380e210a0e9595f61eb3a300aaef9f34', - '0x2ee89372d00fd0e32a46d513f7a80a1ae64302f33bc4b100384327a443c0193c', - '0x2b0e285154aef9e8af0777190947379df37da05cf342897bf1de1bc40e497893', - '0x158b022dd94b2c5c44994a5be28b2f570f1187277430ed9307517fa0c830d432', - '0x1d1ea6f83308f30e544948e221d6b313367eccfe54ec05dfa757f023b5758f3d', - '0x1a08a4549273627eadafe47379be8e997306f5b9567618b38c93a0d58eb6c54c', - '0xf434e5d987974afdd7f45a0f84fb800ecbbcdf2eeb302e415371e1d08ba4ad7', - '0x168b5b6d46176887125f13423384b8e8dd4fd947aac832d8d15b87865580b5fb', - '0x166cd223e74511332e2df4e7ad7a82c3871ed0305a5708521702c5e62e11a30b', - '0x10f0979b9797e30f8fe15539518c7f4dfc98c7acb1490da60088b6ff908a4876', - '0x20e08df88bbafc9a810fa8e2324c36b5513134477207763849ed4a0b6bd9639', - '0x1e977a84137396a3cfb17565ecfb5b60dffb242c7aab4afecaa45ebd2c83e0a3', - '0x19f3f9b6c6868a0e2a7453ff8949323715817869f8a25075308aa34a50c1ca3c', - '0x248b030bbfab25516cca23e7937d4b3b46967292ef6dfd3df25fcfe289d53fac', - '0x26bee4a0a5c8b76caa6b73172fa7760bd634c28d2c2384335b74f5d18e3933f4', - '0x106719993b9dacbe46b17f4e896c0c9c116d226c50afe2256dca1e81cd510b5c', - '0x19b5748fd961f755dd3c713d09014bd12adbb739fa1d2160067a312780a146a2' - ], - recursiveAggregationInput: [] - }; - let verifier: VerifierTest; - - before(async function () { - const verifierFactory = await hardhat.ethers.getContractFactory('VerifierTest'); - const verifierContract = await verifierFactory.deploy(); - verifier = VerifierTestFactory.connect(verifierContract.address, verifierContract.signer); +import * as hardhat from "hardhat"; +import { expect } from "chai"; +import type { VerifierTest, VerifierRecursiveTest } from "../../typechain"; +import { VerifierTestFactory } from "../../typechain"; +import { getCallRevertReason } from "./utils"; +import { ethers } from "hardhat"; + +describe("Verifier test", function () { + const Q_MOD = "21888242871839275222246405745257275088696311157297823662689037894645226208583"; + const R_MOD = "21888242871839275222246405745257275088548364400416034343698204186575808495617"; + + const PROOF = { + publicInputs: ["0xa3dd954bb76c1474c1a04f04870cc75bcaf66ec23c0303c87fb119f9"], + serializedProof: [ + "0x162e0e35310fa1265df0051490fad590e875a98b4e7781ce1bb2698887e24070", + "0x1a3645718b688a382a00b99059f9488daf624d04ceb39b5553f0a1a0d508dde6", + "0x44df31be22763cde0700cc784f70758b944096a11c9b32bfb4f559d9b6a9567", + "0x2efae700419dd3fa0bebf5404efef2f3b5f8f2288c595ec219a05607e9971c9", + "0x223e7327348fd30effc617ee9fa7e28117869f149719cf93c20788cb78adc291", + "0x99f67d073880787c73d54bc2509c1611ac6f48fbe3b5214b4dc2f3cb3a572c0", + "0x17365bde1bbcd62561764ddd8b2d562edbe1c07519cd23f03831b694c6665a2d", + "0x2f321ac8e18ab998f8fe370f3b5114598881798ccc6eac24d7f4161c15fdabb3", + "0x2f6b4b0f4973f2f6e2fa5ecd34602b20b56f0e4fb551b011af96e555fdc1197d", + "0xb8d070fec07e8467425605015acba755f54db7f566c6704818408d927419d80", + "0x103185cff27eef6e8090373749a8065129fcc93482bd6ea4db1808725b6da2e", + "0x29b35d35c22deda2ac9dd56a9f6a145871b1b6557e165296f804297160d5f98b", + "0x240bb4b0b7e30e71e8af2d908e72bf47b6496aab1e1f7cb32f2604d79f76cff8", + "0x1cd2156a0f0c1944a8a3359618ff978b27eb42075c667960817be624ce161489", + "0xbd0b75112591ab1b4a6a3e03fb76368419b78e4b95ee773b8ef5e7848695cf7", + "0xcd1da7fcfc27d2d9e9743e80951694995b162298d4109428fcf1c9a90f24905", + "0x2672327da3fdec6c58e8a0d33ca94e059da0787e9221a2a0ac412692cc962aac", + "0x50e88db23f7582691a0fb7e5c95dd713e54188833fe1d241e3e32a98dfeb0f0", + "0x8dc78ede51774238b0984b02ac7fcf8b0a8dfcb6ca733b90c6b44aac4551057", + "0x2a3167374e2d54e47ce865ef222346adf7a27d4174820a637cf656899238387", + "0x2f161fddcebb9ed8740c14d3a782efcf6f0ad069371194f87bcc04f9e9baf2ee", + "0x25dcf81d1721eab45e86ccfee579eaa4e54a4a80a19edf784f24cc1ee831e58a", + "0x1e483708e664ced677568d93b3b4f505e9d2968f802e04b31873f7d8f635fb0f", + "0x2bf6cdf920d353ba8bda932b72bf6ff6a93aa831274a5dc3ea6ea647a446d18e", + "0x2aa406a77d9143221165e066adfcc9281b9c90afdcee4336eda87f85d2bfe5b", + "0x26fc05b152609664e624a233e52e12252a0cae9d2a86a36717300063faca4b4b", + "0x24579fb180a63e5594644f4726c5af6d091aee4ee64c2c2a37d98f646a9c8d9d", + "0xb34ff9cbae3a9afe40e80a46e7d1419380e210a0e9595f61eb3a300aaef9f34", + "0x2ee89372d00fd0e32a46d513f7a80a1ae64302f33bc4b100384327a443c0193c", + "0x2b0e285154aef9e8af0777190947379df37da05cf342897bf1de1bc40e497893", + "0x158b022dd94b2c5c44994a5be28b2f570f1187277430ed9307517fa0c830d432", + "0x1d1ea6f83308f30e544948e221d6b313367eccfe54ec05dfa757f023b5758f3d", + "0x1a08a4549273627eadafe47379be8e997306f5b9567618b38c93a0d58eb6c54c", + "0xf434e5d987974afdd7f45a0f84fb800ecbbcdf2eeb302e415371e1d08ba4ad7", + "0x168b5b6d46176887125f13423384b8e8dd4fd947aac832d8d15b87865580b5fb", + "0x166cd223e74511332e2df4e7ad7a82c3871ed0305a5708521702c5e62e11a30b", + "0x10f0979b9797e30f8fe15539518c7f4dfc98c7acb1490da60088b6ff908a4876", + "0x20e08df88bbafc9a810fa8e2324c36b5513134477207763849ed4a0b6bd9639", + "0x1e977a84137396a3cfb17565ecfb5b60dffb242c7aab4afecaa45ebd2c83e0a3", + "0x19f3f9b6c6868a0e2a7453ff8949323715817869f8a25075308aa34a50c1ca3c", + "0x248b030bbfab25516cca23e7937d4b3b46967292ef6dfd3df25fcfe289d53fac", + "0x26bee4a0a5c8b76caa6b73172fa7760bd634c28d2c2384335b74f5d18e3933f4", + "0x106719993b9dacbe46b17f4e896c0c9c116d226c50afe2256dca1e81cd510b5c", + "0x19b5748fd961f755dd3c713d09014bd12adbb739fa1d2160067a312780a146a2", + ], + recursiveAggregationInput: [], + }; + let verifier: VerifierTest; + + before(async function () { + const verifierFactory = await hardhat.ethers.getContractFactory("VerifierTest"); + const verifierContract = await verifierFactory.deploy(); + verifier = VerifierTestFactory.connect(verifierContract.address, verifierContract.signer); + }); + + it("Should verify proof", async () => { + // Call the verifier directly (though the call, not static call) to add the save the consumed gas into the statistic. + const calldata = verifier.interface.encodeFunctionData("verify", [ + PROOF.publicInputs, + PROOF.serializedProof, + PROOF.recursiveAggregationInput, + ]); + await verifier.fallback({ data: calldata }); + + // Check that proof is verified + const result = await verifier.verify(PROOF.publicInputs, PROOF.serializedProof, PROOF.recursiveAggregationInput); + expect(result, "proof verification failed").true; + }); + + describe("Should verify valid proof with fields values in non standard format", function () { + it("Public input with dirty bits over Fr mask", async () => { + const validProof = JSON.parse(JSON.stringify(PROOF)); + // Fill dirty bits + validProof.publicInputs[0] = ethers.BigNumber.from(validProof.publicInputs[0]) + .add("0xe000000000000000000000000000000000000000000000000000000000000000") + .toHexString(); + const result = await verifier.verify( + validProof.publicInputs, + validProof.serializedProof, + validProof.recursiveAggregationInput + ); + expect(result, "proof verification failed").true; }); - it('Should verify proof', async () => { - // Call the verifier directly (though the call, not static call) to add the save the consumed gas into the statistic. - const calldata = verifier.interface.encodeFunctionData('verify', [ - PROOF.publicInputs, - PROOF.serializedProof, - PROOF.recursiveAggregationInput - ]); - await verifier.fallback({ data: calldata }); - - // Check that proof is verified - const result = await verifier.verify( - PROOF.publicInputs, - PROOF.serializedProof, - PROOF.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; + it("Elliptic curve points over modulo", async () => { + const validProof = JSON.parse(JSON.stringify(PROOF)); + // Add modulo to points + validProof.serializedProof[0] = ethers.BigNumber.from(validProof.serializedProof[0]).add(Q_MOD); + validProof.serializedProof[1] = ethers.BigNumber.from(validProof.serializedProof[1]).add(Q_MOD).add(Q_MOD); + const result = await verifier.verify( + validProof.publicInputs, + validProof.serializedProof, + validProof.recursiveAggregationInput + ); + expect(result, "proof verification failed").true; }); - describe('Should verify valid proof with fields values in non standard format', function () { - it('Public input with dirty bits over Fr mask', async () => { - const validProof = JSON.parse(JSON.stringify(PROOF)); - // Fill dirty bits - validProof.publicInputs[0] = ethers.BigNumber.from(validProof.publicInputs[0]) - .add('0xe000000000000000000000000000000000000000000000000000000000000000') - .toHexString(); - const result = await verifier.verify( - validProof.publicInputs, - validProof.serializedProof, - validProof.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; - }); - - it('Elliptic curve points over modulo', async () => { - const validProof = JSON.parse(JSON.stringify(PROOF)); - // Add modulo to points - validProof.serializedProof[0] = ethers.BigNumber.from(validProof.serializedProof[0]).add(Q_MOD); - validProof.serializedProof[1] = ethers.BigNumber.from(validProof.serializedProof[1]).add(Q_MOD).add(Q_MOD); - const result = await verifier.verify( - validProof.publicInputs, - validProof.serializedProof, - validProof.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; - }); - - it('Fr over modulo', async () => { - const validProof = JSON.parse(JSON.stringify(PROOF)); - // Add modulo to number - validProof.serializedProof[22] = ethers.BigNumber.from(validProof.serializedProof[22]).add(R_MOD); - const result = await verifier.verify( - validProof.publicInputs, - validProof.serializedProof, - validProof.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; - }); + it("Fr over modulo", async () => { + const validProof = JSON.parse(JSON.stringify(PROOF)); + // Add modulo to number + validProof.serializedProof[22] = ethers.BigNumber.from(validProof.serializedProof[22]).add(R_MOD); + const result = await verifier.verify( + validProof.publicInputs, + validProof.serializedProof, + validProof.recursiveAggregationInput + ); + expect(result, "proof verification failed").true; + }); + }); + + describe("Should revert on invalid input", function () { + it("More than 1 public inputs", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Add one more public input to proof + invalidProof.publicInputs.push(invalidProof.publicInputs[0]); + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); + }); + + it("Empty public inputs", async () => { + const revertReason = await getCallRevertReason( + verifier.verify([], PROOF.serializedProof, PROOF.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); + }); + + it("More than 44 words for proof", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Add one more "serialized proof" input + invalidProof.serializedProof.push(invalidProof.serializedProof[0]); + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - describe('Should revert on invalid input', function () { - it('More than 1 public inputs', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Add one more public input to proof - invalidProof.publicInputs.push(invalidProof.publicInputs[0]); - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Empty public inputs', async () => { - const revertReason = await getCallRevertReason( - verifier.verify([], PROOF.serializedProof, PROOF.recursiveAggregationInput) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('More than 44 words for proof', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Add one more "serialized proof" input - invalidProof.serializedProof.push(invalidProof.serializedProof[0]); - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Empty serialized proof', async () => { - const revertReason = await getCallRevertReason( - verifier.verify(PROOF.publicInputs, [], PROOF.recursiveAggregationInput) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Not empty recursive aggregation input', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Add one more "recursive aggregation input" value - invalidProof.recursiveAggregationInput.push(invalidProof.publicInputs[0]); - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Elliptic curve point at infinity', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Change first point to point at infinity (encode as (0, 0) on EVM) - invalidProof.serializedProof[0] = ethers.constants.HashZero; - invalidProof.serializedProof[1] = ethers.constants.HashZero; - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); + it("Empty serialized proof", async () => { + const revertReason = await getCallRevertReason( + verifier.verify(PROOF.publicInputs, [], PROOF.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - it('Should failed with invalid public input', async () => { - const revertReason = await getCallRevertReason( - verifier.verify([ethers.constants.HashZero], PROOF.serializedProof, PROOF.recursiveAggregationInput) - ); - expect(revertReason).equal('invalid quotient evaluation'); + it("Not empty recursive aggregation input", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Add one more "recursive aggregation input" value + invalidProof.recursiveAggregationInput.push(invalidProof.publicInputs[0]); + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - it('Should return correct Verification key hash', async () => { - const vksHash = await verifier.verificationKeyHash(); - expect(vksHash).equal('0x6625fa96781746787b58306d414b1e25bd706d37d883a9b3acf57b2bd5e0de52'); + it("Elliptic curve point at infinity", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Change first point to point at infinity (encode as (0, 0) on EVM) + invalidProof.serializedProof[0] = ethers.constants.HashZero; + invalidProof.serializedProof[1] = ethers.constants.HashZero; + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); + }); + + it("Should failed with invalid public input", async () => { + const revertReason = await getCallRevertReason( + verifier.verify([ethers.constants.HashZero], PROOF.serializedProof, PROOF.recursiveAggregationInput) + ); + expect(revertReason).equal("invalid quotient evaluation"); + }); + + it("Should return correct Verification key hash", async () => { + const vksHash = await verifier.verificationKeyHash(); + expect(vksHash).equal("0x6625fa96781746787b58306d414b1e25bd706d37d883a9b3acf57b2bd5e0de52"); + }); }); -describe('Verifier with recursive part test', function () { - const Q_MOD = '21888242871839275222246405745257275088696311157297823662689037894645226208583'; - const R_MOD = '21888242871839275222246405745257275088548364400416034343698204186575808495617'; - - const PROOF = { - publicInputs: ['0xa3dd954bb76c1474c1a04f04870cc75bcaf66ec23c0303c87fb119f9'], - serializedProof: [ - '0x162e0e35310fa1265df0051490fad590e875a98b4e7781ce1bb2698887e24070', - '0x1a3645718b688a382a00b99059f9488daf624d04ceb39b5553f0a1a0d508dde6', - '0x44df31be22763cde0700cc784f70758b944096a11c9b32bfb4f559d9b6a9567', - '0x2efae700419dd3fa0bebf5404efef2f3b5f8f2288c595ec219a05607e9971c9', - '0x223e7327348fd30effc617ee9fa7e28117869f149719cf93c20788cb78adc291', - '0x99f67d073880787c73d54bc2509c1611ac6f48fbe3b5214b4dc2f3cb3a572c0', - '0x17365bde1bbcd62561764ddd8b2d562edbe1c07519cd23f03831b694c6665a2d', - '0x2f321ac8e18ab998f8fe370f3b5114598881798ccc6eac24d7f4161c15fdabb3', - '0x2f6b4b0f4973f2f6e2fa5ecd34602b20b56f0e4fb551b011af96e555fdc1197d', - '0xb8d070fec07e8467425605015acba755f54db7f566c6704818408d927419d80', - '0x103185cff27eef6e8090373749a8065129fcc93482bd6ea4db1808725b6da2e', - '0x29b35d35c22deda2ac9dd56a9f6a145871b1b6557e165296f804297160d5f98b', - '0x240bb4b0b7e30e71e8af2d908e72bf47b6496aab1e1f7cb32f2604d79f76cff8', - '0x1cd2156a0f0c1944a8a3359618ff978b27eb42075c667960817be624ce161489', - '0xbd0b75112591ab1b4a6a3e03fb76368419b78e4b95ee773b8ef5e7848695cf7', - '0xcd1da7fcfc27d2d9e9743e80951694995b162298d4109428fcf1c9a90f24905', - '0x2672327da3fdec6c58e8a0d33ca94e059da0787e9221a2a0ac412692cc962aac', - '0x50e88db23f7582691a0fb7e5c95dd713e54188833fe1d241e3e32a98dfeb0f0', - '0x8dc78ede51774238b0984b02ac7fcf8b0a8dfcb6ca733b90c6b44aac4551057', - '0x2a3167374e2d54e47ce865ef222346adf7a27d4174820a637cf656899238387', - '0x2f161fddcebb9ed8740c14d3a782efcf6f0ad069371194f87bcc04f9e9baf2ee', - '0x25dcf81d1721eab45e86ccfee579eaa4e54a4a80a19edf784f24cc1ee831e58a', - '0x1e483708e664ced677568d93b3b4f505e9d2968f802e04b31873f7d8f635fb0f', - '0x2bf6cdf920d353ba8bda932b72bf6ff6a93aa831274a5dc3ea6ea647a446d18e', - '0x2aa406a77d9143221165e066adfcc9281b9c90afdcee4336eda87f85d2bfe5b', - '0x26fc05b152609664e624a233e52e12252a0cae9d2a86a36717300063faca4b4b', - '0x24579fb180a63e5594644f4726c5af6d091aee4ee64c2c2a37d98f646a9c8d9d', - '0xb34ff9cbae3a9afe40e80a46e7d1419380e210a0e9595f61eb3a300aaef9f34', - '0x2ee89372d00fd0e32a46d513f7a80a1ae64302f33bc4b100384327a443c0193c', - '0x2b0e285154aef9e8af0777190947379df37da05cf342897bf1de1bc40e497893', - '0x158b022dd94b2c5c44994a5be28b2f570f1187277430ed9307517fa0c830d432', - '0x1d1ea6f83308f30e544948e221d6b313367eccfe54ec05dfa757f023b5758f3d', - '0x1a08a4549273627eadafe47379be8e997306f5b9567618b38c93a0d58eb6c54c', - '0xf434e5d987974afdd7f45a0f84fb800ecbbcdf2eeb302e415371e1d08ba4ad7', - '0x168b5b6d46176887125f13423384b8e8dd4fd947aac832d8d15b87865580b5fb', - '0x166cd223e74511332e2df4e7ad7a82c3871ed0305a5708521702c5e62e11a30b', - '0x10f0979b9797e30f8fe15539518c7f4dfc98c7acb1490da60088b6ff908a4876', - '0x20e08df88bbafc9a810fa8e2324c36b5513134477207763849ed4a0b6bd9639', - '0x1e977a84137396a3cfb17565ecfb5b60dffb242c7aab4afecaa45ebd2c83e0a3', - '0x19f3f9b6c6868a0e2a7453ff8949323715817869f8a25075308aa34a50c1ca3c', - '0x248b030bbfab25516cca23e7937d4b3b46967292ef6dfd3df25fcfe289d53fac', - '0x26bee4a0a5c8b76caa6b73172fa7760bd634c28d2c2384335b74f5d18e3933f4', - '0x106719993b9dacbe46b17f4e896c0c9c116d226c50afe2256dca1e81cd510b5c', - '0x19b5748fd961f755dd3c713d09014bd12adbb739fa1d2160067a312780a146a2' - ], - recursiveAggregationInput: [ - '0x04fdf01a2faedb9e3a620bc1cd8ceb4b0adac04631bdfa9e7e9fc15e35693cc0', - '0x1419728b438cc9afa63ab4861753e0798e29e08aac0da17b2c7617b994626ca2', - '0x23ca418458f6bdc30dfdbc13b80c604f8864619582eb247d09c8e4703232897b', - '0x0713c1371914ac18d7dced467a8a60eeca0f3d80a2cbd5dcc75abb6cbab39f39' - ] - }; - let verifier: VerifierRecursiveTest; - - before(async function () { - const verifierFactory = await hardhat.ethers.getContractFactory('VerifierRecursiveTest'); - const verifierContract = await verifierFactory.deploy(); - verifier = VerifierTestFactory.connect(verifierContract.address, verifierContract.signer); +describe("Verifier with recursive part test", function () { + const Q_MOD = "21888242871839275222246405745257275088696311157297823662689037894645226208583"; + const R_MOD = "21888242871839275222246405745257275088548364400416034343698204186575808495617"; + + const PROOF = { + publicInputs: ["0xa3dd954bb76c1474c1a04f04870cc75bcaf66ec23c0303c87fb119f9"], + serializedProof: [ + "0x162e0e35310fa1265df0051490fad590e875a98b4e7781ce1bb2698887e24070", + "0x1a3645718b688a382a00b99059f9488daf624d04ceb39b5553f0a1a0d508dde6", + "0x44df31be22763cde0700cc784f70758b944096a11c9b32bfb4f559d9b6a9567", + "0x2efae700419dd3fa0bebf5404efef2f3b5f8f2288c595ec219a05607e9971c9", + "0x223e7327348fd30effc617ee9fa7e28117869f149719cf93c20788cb78adc291", + "0x99f67d073880787c73d54bc2509c1611ac6f48fbe3b5214b4dc2f3cb3a572c0", + "0x17365bde1bbcd62561764ddd8b2d562edbe1c07519cd23f03831b694c6665a2d", + "0x2f321ac8e18ab998f8fe370f3b5114598881798ccc6eac24d7f4161c15fdabb3", + "0x2f6b4b0f4973f2f6e2fa5ecd34602b20b56f0e4fb551b011af96e555fdc1197d", + "0xb8d070fec07e8467425605015acba755f54db7f566c6704818408d927419d80", + "0x103185cff27eef6e8090373749a8065129fcc93482bd6ea4db1808725b6da2e", + "0x29b35d35c22deda2ac9dd56a9f6a145871b1b6557e165296f804297160d5f98b", + "0x240bb4b0b7e30e71e8af2d908e72bf47b6496aab1e1f7cb32f2604d79f76cff8", + "0x1cd2156a0f0c1944a8a3359618ff978b27eb42075c667960817be624ce161489", + "0xbd0b75112591ab1b4a6a3e03fb76368419b78e4b95ee773b8ef5e7848695cf7", + "0xcd1da7fcfc27d2d9e9743e80951694995b162298d4109428fcf1c9a90f24905", + "0x2672327da3fdec6c58e8a0d33ca94e059da0787e9221a2a0ac412692cc962aac", + "0x50e88db23f7582691a0fb7e5c95dd713e54188833fe1d241e3e32a98dfeb0f0", + "0x8dc78ede51774238b0984b02ac7fcf8b0a8dfcb6ca733b90c6b44aac4551057", + "0x2a3167374e2d54e47ce865ef222346adf7a27d4174820a637cf656899238387", + "0x2f161fddcebb9ed8740c14d3a782efcf6f0ad069371194f87bcc04f9e9baf2ee", + "0x25dcf81d1721eab45e86ccfee579eaa4e54a4a80a19edf784f24cc1ee831e58a", + "0x1e483708e664ced677568d93b3b4f505e9d2968f802e04b31873f7d8f635fb0f", + "0x2bf6cdf920d353ba8bda932b72bf6ff6a93aa831274a5dc3ea6ea647a446d18e", + "0x2aa406a77d9143221165e066adfcc9281b9c90afdcee4336eda87f85d2bfe5b", + "0x26fc05b152609664e624a233e52e12252a0cae9d2a86a36717300063faca4b4b", + "0x24579fb180a63e5594644f4726c5af6d091aee4ee64c2c2a37d98f646a9c8d9d", + "0xb34ff9cbae3a9afe40e80a46e7d1419380e210a0e9595f61eb3a300aaef9f34", + "0x2ee89372d00fd0e32a46d513f7a80a1ae64302f33bc4b100384327a443c0193c", + "0x2b0e285154aef9e8af0777190947379df37da05cf342897bf1de1bc40e497893", + "0x158b022dd94b2c5c44994a5be28b2f570f1187277430ed9307517fa0c830d432", + "0x1d1ea6f83308f30e544948e221d6b313367eccfe54ec05dfa757f023b5758f3d", + "0x1a08a4549273627eadafe47379be8e997306f5b9567618b38c93a0d58eb6c54c", + "0xf434e5d987974afdd7f45a0f84fb800ecbbcdf2eeb302e415371e1d08ba4ad7", + "0x168b5b6d46176887125f13423384b8e8dd4fd947aac832d8d15b87865580b5fb", + "0x166cd223e74511332e2df4e7ad7a82c3871ed0305a5708521702c5e62e11a30b", + "0x10f0979b9797e30f8fe15539518c7f4dfc98c7acb1490da60088b6ff908a4876", + "0x20e08df88bbafc9a810fa8e2324c36b5513134477207763849ed4a0b6bd9639", + "0x1e977a84137396a3cfb17565ecfb5b60dffb242c7aab4afecaa45ebd2c83e0a3", + "0x19f3f9b6c6868a0e2a7453ff8949323715817869f8a25075308aa34a50c1ca3c", + "0x248b030bbfab25516cca23e7937d4b3b46967292ef6dfd3df25fcfe289d53fac", + "0x26bee4a0a5c8b76caa6b73172fa7760bd634c28d2c2384335b74f5d18e3933f4", + "0x106719993b9dacbe46b17f4e896c0c9c116d226c50afe2256dca1e81cd510b5c", + "0x19b5748fd961f755dd3c713d09014bd12adbb739fa1d2160067a312780a146a2", + ], + recursiveAggregationInput: [ + "0x04fdf01a2faedb9e3a620bc1cd8ceb4b0adac04631bdfa9e7e9fc15e35693cc0", + "0x1419728b438cc9afa63ab4861753e0798e29e08aac0da17b2c7617b994626ca2", + "0x23ca418458f6bdc30dfdbc13b80c604f8864619582eb247d09c8e4703232897b", + "0x0713c1371914ac18d7dced467a8a60eeca0f3d80a2cbd5dcc75abb6cbab39f39", + ], + }; + let verifier: VerifierRecursiveTest; + + before(async function () { + const verifierFactory = await hardhat.ethers.getContractFactory("VerifierRecursiveTest"); + const verifierContract = await verifierFactory.deploy(); + verifier = VerifierTestFactory.connect(verifierContract.address, verifierContract.signer); + }); + + it("Should verify proof", async () => { + // Call the verifier directly (though the call, not static call) to add the save the consumed gas into the statistic. + const calldata = verifier.interface.encodeFunctionData("verify", [ + PROOF.publicInputs, + PROOF.serializedProof, + PROOF.recursiveAggregationInput, + ]); + await verifier.fallback({ data: calldata }); + + // Check that proof is verified + const result = await verifier.verify(PROOF.publicInputs, PROOF.serializedProof, PROOF.recursiveAggregationInput); + expect(result, "proof verification failed").true; + }); + + describe("Should verify valid proof with fields values in non standard format", function () { + it("Public input with dirty bits over Fr mask", async () => { + const validProof = JSON.parse(JSON.stringify(PROOF)); + // Fill dirty bits + validProof.publicInputs[0] = ethers.BigNumber.from(validProof.publicInputs[0]) + .add("0xe000000000000000000000000000000000000000000000000000000000000000") + .toHexString(); + const result = await verifier.verify( + validProof.publicInputs, + validProof.serializedProof, + validProof.recursiveAggregationInput + ); + expect(result, "proof verification failed").true; + }); + + it("Elliptic curve points over modulo", async () => { + const validProof = JSON.parse(JSON.stringify(PROOF)); + // Add modulo to points + validProof.serializedProof[0] = ethers.BigNumber.from(validProof.serializedProof[0]).add(Q_MOD); + validProof.serializedProof[1] = ethers.BigNumber.from(validProof.serializedProof[1]).add(Q_MOD).add(Q_MOD); + const result = await verifier.verify( + validProof.publicInputs, + validProof.serializedProof, + validProof.recursiveAggregationInput + ); + expect(result, "proof verification failed").true; + }); + + it("Fr over modulo", async () => { + const validProof = JSON.parse(JSON.stringify(PROOF)); + // Add modulo to number + validProof.serializedProof[22] = ethers.BigNumber.from(validProof.serializedProof[22]).add(R_MOD); + const result = await verifier.verify( + validProof.publicInputs, + validProof.serializedProof, + validProof.recursiveAggregationInput + ); + expect(result, "proof verification failed").true; + }); + }); + + describe("Should revert on invalid input", function () { + it("More than 1 public inputs", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Add one more public input to proof + invalidProof.publicInputs.push(invalidProof.publicInputs[0]); + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - it('Should verify proof', async () => { - // Call the verifier directly (though the call, not static call) to add the save the consumed gas into the statistic. - const calldata = verifier.interface.encodeFunctionData('verify', [ - PROOF.publicInputs, - PROOF.serializedProof, - PROOF.recursiveAggregationInput - ]); - await verifier.fallback({ data: calldata }); - - // Check that proof is verified - const result = await verifier.verify( - PROOF.publicInputs, - PROOF.serializedProof, - PROOF.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; + it("Empty public inputs", async () => { + const revertReason = await getCallRevertReason( + verifier.verify([], PROOF.serializedProof, PROOF.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - describe('Should verify valid proof with fields values in non standard format', function () { - it('Public input with dirty bits over Fr mask', async () => { - const validProof = JSON.parse(JSON.stringify(PROOF)); - // Fill dirty bits - validProof.publicInputs[0] = ethers.BigNumber.from(validProof.publicInputs[0]) - .add('0xe000000000000000000000000000000000000000000000000000000000000000') - .toHexString(); - const result = await verifier.verify( - validProof.publicInputs, - validProof.serializedProof, - validProof.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; - }); - - it('Elliptic curve points over modulo', async () => { - const validProof = JSON.parse(JSON.stringify(PROOF)); - // Add modulo to points - validProof.serializedProof[0] = ethers.BigNumber.from(validProof.serializedProof[0]).add(Q_MOD); - validProof.serializedProof[1] = ethers.BigNumber.from(validProof.serializedProof[1]).add(Q_MOD).add(Q_MOD); - const result = await verifier.verify( - validProof.publicInputs, - validProof.serializedProof, - validProof.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; - }); - - it('Fr over modulo', async () => { - const validProof = JSON.parse(JSON.stringify(PROOF)); - // Add modulo to number - validProof.serializedProof[22] = ethers.BigNumber.from(validProof.serializedProof[22]).add(R_MOD); - const result = await verifier.verify( - validProof.publicInputs, - validProof.serializedProof, - validProof.recursiveAggregationInput - ); - expect(result, 'proof verification failed').true; - }); + it("More than 44 words for proof", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Add one more "serialized proof" input + invalidProof.serializedProof.push(invalidProof.serializedProof[0]); + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - describe('Should revert on invalid input', function () { - it('More than 1 public inputs', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Add one more public input to proof - invalidProof.publicInputs.push(invalidProof.publicInputs[0]); - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Empty public inputs', async () => { - const revertReason = await getCallRevertReason( - verifier.verify([], PROOF.serializedProof, PROOF.recursiveAggregationInput) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('More than 44 words for proof', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Add one more "serialized proof" input - invalidProof.serializedProof.push(invalidProof.serializedProof[0]); - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Empty serialized proof', async () => { - const revertReason = await getCallRevertReason( - verifier.verify(PROOF.publicInputs, [], PROOF.recursiveAggregationInput) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('More than 4 words for recursive aggregation input', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Add one more "recursive aggregation input" value - invalidProof.recursiveAggregationInput.push(invalidProof.recursiveAggregationInput[0]); - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Empty recursive aggregation input', async () => { - const revertReason = await getCallRevertReason( - verifier.verify(PROOF.publicInputs, PROOF.serializedProof, []) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); - - it('Elliptic curve point at infinity', async () => { - const invalidProof = JSON.parse(JSON.stringify(PROOF)); - // Change first point to point at infinity (encode as (0, 0) on EVM) - invalidProof.serializedProof[0] = ethers.constants.HashZero; - invalidProof.serializedProof[1] = ethers.constants.HashZero; - const revertReason = await getCallRevertReason( - verifier.verify( - invalidProof.publicInputs, - invalidProof.serializedProof, - invalidProof.recursiveAggregationInput - ) - ); - expect(revertReason).equal('loadProof: Proof is invalid'); - }); + it("Empty serialized proof", async () => { + const revertReason = await getCallRevertReason( + verifier.verify(PROOF.publicInputs, [], PROOF.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - it('Should failed with invalid public input', async () => { - const revertReason = await getCallRevertReason( - verifier.verify([ethers.constants.HashZero], PROOF.serializedProof, PROOF.recursiveAggregationInput) - ); - expect(revertReason).equal('invalid quotient evaluation'); + it("More than 4 words for recursive aggregation input", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Add one more "recursive aggregation input" value + invalidProof.recursiveAggregationInput.push(invalidProof.recursiveAggregationInput[0]); + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - it('Should failed with invalid recursive aggregative input', async () => { - const revertReason = await getCallRevertReason( - verifier.verify(PROOF.publicInputs, PROOF.serializedProof, [1, 2, 1, 2]) - ); - expect(revertReason).equal('finalPairing: pairing failure'); + it("Empty recursive aggregation input", async () => { + const revertReason = await getCallRevertReason(verifier.verify(PROOF.publicInputs, PROOF.serializedProof, [])); + expect(revertReason).equal("loadProof: Proof is invalid"); }); - it('Should return correct Verification key hash', async () => { - const vksHash = await verifier.verificationKeyHash(); - expect(vksHash).equal('0x88b3ddc4ed85974c7e14297dcad4097169440305c05fdb6441ca8dfd77cd7fa7'); + it("Elliptic curve point at infinity", async () => { + const invalidProof = JSON.parse(JSON.stringify(PROOF)); + // Change first point to point at infinity (encode as (0, 0) on EVM) + invalidProof.serializedProof[0] = ethers.constants.HashZero; + invalidProof.serializedProof[1] = ethers.constants.HashZero; + const revertReason = await getCallRevertReason( + verifier.verify(invalidProof.publicInputs, invalidProof.serializedProof, invalidProof.recursiveAggregationInput) + ); + expect(revertReason).equal("loadProof: Proof is invalid"); }); + }); + + it("Should failed with invalid public input", async () => { + const revertReason = await getCallRevertReason( + verifier.verify([ethers.constants.HashZero], PROOF.serializedProof, PROOF.recursiveAggregationInput) + ); + expect(revertReason).equal("invalid quotient evaluation"); + }); + + it("Should failed with invalid recursive aggregative input", async () => { + const revertReason = await getCallRevertReason( + verifier.verify(PROOF.publicInputs, PROOF.serializedProof, [1, 2, 1, 2]) + ); + expect(revertReason).equal("finalPairing: pairing failure"); + }); + + it("Should return correct Verification key hash", async () => { + const vksHash = await verifier.verificationKeyHash(); + expect(vksHash).equal("0x88b3ddc4ed85974c7e14297dcad4097169440305c05fdb6441ca8dfd77cd7fa7"); + }); }); diff --git a/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts b/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts index 1bf6a0e09..9098dd451 100644 --- a/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts +++ b/l1-contracts/test/unit_tests/zksync-upgrade.fork.ts @@ -1,174 +1,174 @@ -import * as hardhat from 'hardhat'; -import { expect } from 'chai'; -import { facetCut, Action, getAllSelectors } from '../../src.ts/diamondCut'; - -import { IDiamondCutFactory } from '../../typechain/IDiamondCutFactory'; -import type { IDiamondCut } from '../../typechain/IDiamondCut'; -import { IExecutorFactory } from '../../typechain/IExecutorFactory'; -import type { IExecutor } from '../../typechain/IExecutor'; -import { IGettersFactory } from '../../typechain/IGettersFactory'; -import type { IGetters } from '../../typechain/IGetters'; -import { IGovernanceFactory } from '../../typechain/IGovernanceFactory'; -import type { IGovernance } from '../../typechain/IGovernance'; -import { IMailboxFactory } from '../../typechain/IMailboxFactory'; -import type { IMailbox } from '../../typechain/IMailbox'; -import { IZkSyncFactory } from '../../typechain/IZkSyncFactory'; -import type { IZkSync } from '../../typechain/IZkSync'; -import { ethers } from 'ethers'; +import * as hardhat from "hardhat"; +import { expect } from "chai"; +import { facetCut, Action, getAllSelectors } from "../../src.ts/diamondCut"; + +import { IDiamondCutFactory } from "../../typechain/IDiamondCutFactory"; +import type { IDiamondCut } from "../../typechain/IDiamondCut"; +import { IExecutorFactory } from "../../typechain/IExecutorFactory"; +import type { IExecutor } from "../../typechain/IExecutor"; +import { IGettersFactory } from "../../typechain/IGettersFactory"; +import type { IGetters } from "../../typechain/IGetters"; +import { IGovernanceFactory } from "../../typechain/IGovernanceFactory"; +import type { IGovernance } from "../../typechain/IGovernance"; +import { IMailboxFactory } from "../../typechain/IMailboxFactory"; +import type { IMailbox } from "../../typechain/IMailbox"; +import { IZkSyncFactory } from "../../typechain/IZkSyncFactory"; +import type { IZkSync } from "../../typechain/IZkSync"; +import { ethers } from "ethers"; // TODO: change to the mainet config -const DIAMOND_PROXY_ADDRESS = '0x1908e2BF4a88F91E4eF0DC72f02b8Ea36BEa2319'; - -describe('Diamond proxy upgrade fork test', function () { - let governor: ethers.Signer; - let diamondProxy: IZkSync; - - let newDiamondCutFacet: IDiamondCut; - let newExecutorFacet: IExecutor; - let newGettersFacet: IGetters; - let newGovernanceFacet: IGovernance; - let newMailboxFacet: IMailbox; - - let diamondCutData; - - before(async () => { - const signers = await hardhat.ethers.getSigners(); - diamondProxy = IZkSyncFactory.connect(DIAMOND_PROXY_ADDRESS, signers[0]); - const governorAddress = await diamondProxy.getGovernor(); - - await hardhat.network.provider.request({ method: 'hardhat_impersonateAccount', params: [governorAddress] }); - governor = await hardhat.ethers.provider.getSigner(governorAddress); - - await hardhat.network.provider.send('hardhat_setBalance', [governorAddress, '0xfffffffffffffffff']); - - const diamondCutFacetFactory = await hardhat.ethers.getContractFactory('DiamondCutFacet'); - const diamondCutFacet = await diamondCutFacetFactory.deploy(); - newDiamondCutFacet = IDiamondCutFactory.connect(diamondCutFacet.address, diamondCutFacet.signer); - - const executorFacetFactory = await hardhat.ethers.getContractFactory('ExecutorFacet'); - const executorFacet = await executorFacetFactory.deploy(); - newExecutorFacet = IExecutorFactory.connect(executorFacet.address, executorFacet.signer); - - const gettersFacetFactory = await hardhat.ethers.getContractFactory('GettersFacet'); - const gettersFacet = await gettersFacetFactory.deploy(); - newGettersFacet = IGettersFactory.connect(gettersFacet.address, gettersFacet.signer); - - const governanceFacetFactory = await hardhat.ethers.getContractFactory('GovernanceFacet'); - const governanceFacet = await governanceFacetFactory.deploy(); - newGovernanceFacet = IGovernanceFactory.connect(governanceFacet.address, governanceFacet.signer); - - const mailboxFacetFactory = await hardhat.ethers.getContractFactory('MailboxFacet'); - const mailboxFacet = await mailboxFacetFactory.deploy(); - newMailboxFacet = IMailboxFactory.connect(mailboxFacet.address, mailboxFacet.signer); - - // If the upgrade is already running, then cancel it to start upgrading over. - const currentUpgradeStatus = await diamondProxy.getUpgradeProposalState(); - if (currentUpgradeStatus != 0) { - const upgradeProposalHash = await diamondProxy.getProposedUpgradeHash(); - await diamondProxy.connect(governorAddress).cancelUpgradeProposal(upgradeProposalHash); - } - - // Prepare diamond cut for upgrade - let facetCuts; +const DIAMOND_PROXY_ADDRESS = "0x1908e2BF4a88F91E4eF0DC72f02b8Ea36BEa2319"; + +describe("Diamond proxy upgrade fork test", function () { + let governor: ethers.Signer; + let diamondProxy: IZkSync; + + let newDiamondCutFacet: IDiamondCut; + let newExecutorFacet: IExecutor; + let newGettersFacet: IGetters; + let newGovernanceFacet: IGovernance; + let newMailboxFacet: IMailbox; + + let diamondCutData; + + before(async () => { + const signers = await hardhat.ethers.getSigners(); + diamondProxy = IZkSyncFactory.connect(DIAMOND_PROXY_ADDRESS, signers[0]); + const governorAddress = await diamondProxy.getGovernor(); + + await hardhat.network.provider.request({ method: "hardhat_impersonateAccount", params: [governorAddress] }); + governor = await hardhat.ethers.provider.getSigner(governorAddress); + + await hardhat.network.provider.send("hardhat_setBalance", [governorAddress, "0xfffffffffffffffff"]); + + const diamondCutFacetFactory = await hardhat.ethers.getContractFactory("DiamondCutFacet"); + const diamondCutFacet = await diamondCutFacetFactory.deploy(); + newDiamondCutFacet = IDiamondCutFactory.connect(diamondCutFacet.address, diamondCutFacet.signer); + + const executorFacetFactory = await hardhat.ethers.getContractFactory("ExecutorFacet"); + const executorFacet = await executorFacetFactory.deploy(); + newExecutorFacet = IExecutorFactory.connect(executorFacet.address, executorFacet.signer); + + const gettersFacetFactory = await hardhat.ethers.getContractFactory("GettersFacet"); + const gettersFacet = await gettersFacetFactory.deploy(); + newGettersFacet = IGettersFactory.connect(gettersFacet.address, gettersFacet.signer); + + const governanceFacetFactory = await hardhat.ethers.getContractFactory("GovernanceFacet"); + const governanceFacet = await governanceFacetFactory.deploy(); + newGovernanceFacet = IGovernanceFactory.connect(governanceFacet.address, governanceFacet.signer); + + const mailboxFacetFactory = await hardhat.ethers.getContractFactory("MailboxFacet"); + const mailboxFacet = await mailboxFacetFactory.deploy(); + newMailboxFacet = IMailboxFactory.connect(mailboxFacet.address, mailboxFacet.signer); + + // If the upgrade is already running, then cancel it to start upgrading over. + const currentUpgradeStatus = await diamondProxy.getUpgradeProposalState(); + if (currentUpgradeStatus != 0) { + const upgradeProposalHash = await diamondProxy.getProposedUpgradeHash(); + await diamondProxy.connect(governorAddress).cancelUpgradeProposal(upgradeProposalHash); + } + + // Prepare diamond cut for upgrade + let facetCuts; + { + const getters = await hardhat.ethers.getContractAt("GettersFacet", newGettersFacet.address); + const diamondCutFacet = await hardhat.ethers.getContractAt("DiamondCutFacet", newDiamondCutFacet.address); + const executor = await hardhat.ethers.getContractAt("ExecutorFacet", newExecutorFacet.address); + const governance = await hardhat.ethers.getContractAt("GovernanceFacet", newGovernanceFacet.address); + const mailbox = await hardhat.ethers.getContractAt("MailboxFacet", newMailboxFacet.address); + + const oldFacets = await diamondProxy.facets(); + const selectorsToRemove = []; + for (let i = 0; i < oldFacets.length; ++i) { + selectorsToRemove.push(...oldFacets[i].selectors); + } + + facetCuts = [ + // Remove old facets { - const getters = await hardhat.ethers.getContractAt('GettersFacet', newGettersFacet.address); - const diamondCutFacet = await hardhat.ethers.getContractAt('DiamondCutFacet', newDiamondCutFacet.address); - const executor = await hardhat.ethers.getContractAt('ExecutorFacet', newExecutorFacet.address); - const governance = await hardhat.ethers.getContractAt('GovernanceFacet', newGovernanceFacet.address); - const mailbox = await hardhat.ethers.getContractAt('MailboxFacet', newMailboxFacet.address); - - const oldFacets = await diamondProxy.facets(); - const selectorsToRemove = []; - for (let i = 0; i < oldFacets.length; ++i) { - selectorsToRemove.push(...oldFacets[i].selectors); - } - - facetCuts = [ - // Remove old facets - { - facet: ethers.constants.AddressZero, - selectors: selectorsToRemove, - action: Action.Remove, - isFreezable: false - }, - // Add new facets - facetCut(diamondCutFacet.address, diamondCutFacet.interface, Action.Add, false), - facetCut(getters.address, getters.interface, Action.Add, false), - facetCut(mailbox.address, mailbox.interface, Action.Add, true), - facetCut(executor.address, executor.interface, Action.Add, true), - facetCut(governance.address, governance.interface, Action.Add, true) - ]; - } - diamondCutData = { - facetCuts, - initAddress: ethers.constants.AddressZero, - initCalldata: [] - }; - }); - - it('should start upgrade', async () => { - const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); - - const expectedProposalId = await diamondProxy.getCurrentProposalId(); - await diamondProxy.connect(governor).proposeTransparentUpgrade(diamondCutData, expectedProposalId.add(1)); - - const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); - - expect(upgradeStatusBefore).eq(0); - expect(upgradeStatusAfter).eq(1); - }); - - it('should finish upgrade', async () => { - const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); - await diamondProxy.connect(governor).executeUpgrade(diamondCutData, ethers.constants.HashZero); - const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); - - expect(upgradeStatusBefore).eq(1); - expect(upgradeStatusAfter).eq(0); - }); - - it('should start second upgrade', async () => { - const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); - - const expectedProposalId = await diamondProxy.getCurrentProposalId(); - await diamondProxy.connect(governor).proposeTransparentUpgrade(diamondCutData, expectedProposalId.add(1)); - - const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); - - expect(upgradeStatusBefore).eq(0); - expect(upgradeStatusAfter).eq(1); - }); - - it('should finish second upgrade', async () => { - const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); - await diamondProxy.connect(governor).executeUpgrade(diamondCutData, ethers.constants.HashZero); - const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); - - expect(upgradeStatusBefore).eq(1); - expect(upgradeStatusAfter).eq(0); - }); - - it('check getters functions', async () => { - const governorAddr = await diamondProxy.getGovernor(); - expect(governorAddr).to.be.eq(await governor.getAddress()); - - const isFrozen = await diamondProxy.isDiamondStorageFrozen(); - expect(isFrozen).to.be.eq(false); - - const getters = await hardhat.ethers.getContractAt('GettersFacet', newGettersFacet.address); - const diamondCutFacet = await hardhat.ethers.getContractAt('DiamondCutFacet', newDiamondCutFacet.address); - const executor = await hardhat.ethers.getContractAt('ExecutorFacet', newExecutorFacet.address); - const governance = await hardhat.ethers.getContractAt('GovernanceFacet', newGovernanceFacet.address); - const mailbox = await hardhat.ethers.getContractAt('MailboxFacet', newMailboxFacet.address); - - const facets = [...(await await diamondProxy.facets())].sort(); - const expectedFacets = [ - [newDiamondCutFacet.address, getAllSelectors(diamondCutFacet.interface)], - [newExecutorFacet.address, getAllSelectors(executor.interface)], - [newGettersFacet.address, getAllSelectors(getters.interface)], - [newGovernanceFacet.address, getAllSelectors(governance.interface)], - [newMailboxFacet.address, getAllSelectors(mailbox.interface)] - ].sort(); - expect(expectedFacets).to.be.eql(facets); - }); + facet: ethers.constants.AddressZero, + selectors: selectorsToRemove, + action: Action.Remove, + isFreezable: false, + }, + // Add new facets + facetCut(diamondCutFacet.address, diamondCutFacet.interface, Action.Add, false), + facetCut(getters.address, getters.interface, Action.Add, false), + facetCut(mailbox.address, mailbox.interface, Action.Add, true), + facetCut(executor.address, executor.interface, Action.Add, true), + facetCut(governance.address, governance.interface, Action.Add, true), + ]; + } + diamondCutData = { + facetCuts, + initAddress: ethers.constants.AddressZero, + initCalldata: [], + }; + }); + + it("should start upgrade", async () => { + const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); + + const expectedProposalId = await diamondProxy.getCurrentProposalId(); + await diamondProxy.connect(governor).proposeTransparentUpgrade(diamondCutData, expectedProposalId.add(1)); + + const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); + + expect(upgradeStatusBefore).eq(0); + expect(upgradeStatusAfter).eq(1); + }); + + it("should finish upgrade", async () => { + const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); + await diamondProxy.connect(governor).executeUpgrade(diamondCutData, ethers.constants.HashZero); + const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); + + expect(upgradeStatusBefore).eq(1); + expect(upgradeStatusAfter).eq(0); + }); + + it("should start second upgrade", async () => { + const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); + + const expectedProposalId = await diamondProxy.getCurrentProposalId(); + await diamondProxy.connect(governor).proposeTransparentUpgrade(diamondCutData, expectedProposalId.add(1)); + + const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); + + expect(upgradeStatusBefore).eq(0); + expect(upgradeStatusAfter).eq(1); + }); + + it("should finish second upgrade", async () => { + const upgradeStatusBefore = await diamondProxy.getUpgradeProposalState(); + await diamondProxy.connect(governor).executeUpgrade(diamondCutData, ethers.constants.HashZero); + const upgradeStatusAfter = await diamondProxy.getUpgradeProposalState(); + + expect(upgradeStatusBefore).eq(1); + expect(upgradeStatusAfter).eq(0); + }); + + it("check getters functions", async () => { + const governorAddr = await diamondProxy.getGovernor(); + expect(governorAddr).to.be.eq(await governor.getAddress()); + + const isFrozen = await diamondProxy.isDiamondStorageFrozen(); + expect(isFrozen).to.be.eq(false); + + const getters = await hardhat.ethers.getContractAt("GettersFacet", newGettersFacet.address); + const diamondCutFacet = await hardhat.ethers.getContractAt("DiamondCutFacet", newDiamondCutFacet.address); + const executor = await hardhat.ethers.getContractAt("ExecutorFacet", newExecutorFacet.address); + const governance = await hardhat.ethers.getContractAt("GovernanceFacet", newGovernanceFacet.address); + const mailbox = await hardhat.ethers.getContractAt("MailboxFacet", newMailboxFacet.address); + + const facets = [...(await await diamondProxy.facets())].sort(); + const expectedFacets = [ + [newDiamondCutFacet.address, getAllSelectors(diamondCutFacet.interface)], + [newExecutorFacet.address, getAllSelectors(executor.interface)], + [newGettersFacet.address, getAllSelectors(getters.interface)], + [newGovernanceFacet.address, getAllSelectors(governance.interface)], + [newMailboxFacet.address, getAllSelectors(mailbox.interface)], + ].sort(); + expect(expectedFacets).to.be.eql(facets); + }); }); diff --git a/l1-contracts/upgrade-system/facets.ts b/l1-contracts/upgrade-system/facets.ts index 690497b69..d6b554d78 100644 --- a/l1-contracts/upgrade-system/facets.ts +++ b/l1-contracts/upgrade-system/facets.ts @@ -1,161 +1,161 @@ -import { Command } from 'commander'; -import type { BigNumber } from 'ethers'; -import { ethers } from 'ethers'; -import * as fs from 'fs'; -import * as path from 'path'; -import { web3Url } from 'zk/build/utils'; -import { deployViaCreate2 } from '../src.ts/deploy-utils'; -import { getFacetCutsForUpgrade } from '../src.ts/diamondCut'; -import { insertGasPrice } from './utils'; +import { Command } from "commander"; +import type { BigNumber } from "ethers"; +import { ethers } from "ethers"; +import * as fs from "fs"; +import * as path from "path"; +import { web3Url } from "zk/build/utils"; +import { deployViaCreate2 } from "../src.ts/deploy-utils"; +import { getFacetCutsForUpgrade } from "../src.ts/diamondCut"; +import { insertGasPrice } from "./utils"; -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function deployFacetCut( - wallet: ethers.Wallet, - name: string, - create2Address: string, - // eslint-disable-next-line @typescript-eslint/ban-types - ethTxOptions: {}, - create2Salt?: string + wallet: ethers.Wallet, + name: string, + create2Address: string, + // eslint-disable-next-line @typescript-eslint/ban-types + ethTxOptions: {}, + create2Salt?: string ) { - create2Salt = create2Salt ?? ethers.constants.HashZero; + create2Salt = create2Salt ?? ethers.constants.HashZero; - ethTxOptions['gasLimit'] = 10_000_000; - const [address, txHash] = await deployViaCreate2(wallet, name, [], create2Salt, ethTxOptions, create2Address, true); + ethTxOptions["gasLimit"] = 10_000_000; + const [address, txHash] = await deployViaCreate2(wallet, name, [], create2Salt, ethTxOptions, create2Address, true); - console.log(`Deployed ${name} at ${address} with txHash ${txHash}`); - return [address, txHash]; + console.log(`Deployed ${name} at ${address} with txHash ${txHash}`); + return [address, txHash]; } async function deployFacetCuts( - l1Rpc: string, - names: string[], - create2Address: string, - file?: string, - privateKey?: string, - nonce?: number, - gasPrice?: BigNumber, - create2Salt?: string + l1Rpc: string, + names: string[], + create2Address: string, + file?: string, + privateKey?: string, + nonce?: number, + gasPrice?: BigNumber, + create2Salt?: string ) { - const provider = new ethers.providers.JsonRpcProvider(l1Rpc); - const wallet = privateKey - ? new ethers.Wallet(privateKey, provider) - : ethers.Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - const deployedFacets = {}; - const ethTxOptions = {}; - if (!nonce) { - ethTxOptions['nonce'] = await wallet.getTransactionCount(); - } else { - ethTxOptions['nonce'] = nonce; - } - if (!gasPrice) { - await insertGasPrice(provider, ethTxOptions); - } - for (let i = 0; i < names.length; i++) { - const [address, txHash] = await deployFacetCut(wallet, names[i], create2Address, ethTxOptions, create2Salt); - ethTxOptions['nonce'] += 1; - deployedFacets[names[i]] = { address, txHash }; - } - console.log(JSON.stringify(deployedFacets, null, 2)); - if (file) { - fs.writeFileSync(file, JSON.stringify(deployedFacets, null, 2)); - } - return deployedFacets; + const provider = new ethers.providers.JsonRpcProvider(l1Rpc); + const wallet = privateKey + ? new ethers.Wallet(privateKey, provider) + : ethers.Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + const deployedFacets = {}; + const ethTxOptions = {}; + if (!nonce) { + ethTxOptions["nonce"] = await wallet.getTransactionCount(); + } else { + ethTxOptions["nonce"] = nonce; + } + if (!gasPrice) { + await insertGasPrice(provider, ethTxOptions); + } + for (let i = 0; i < names.length; i++) { + const [address, txHash] = await deployFacetCut(wallet, names[i], create2Address, ethTxOptions, create2Salt); + ethTxOptions["nonce"] += 1; + deployedFacets[names[i]] = { address, txHash }; + } + console.log(JSON.stringify(deployedFacets, null, 2)); + if (file) { + fs.writeFileSync(file, JSON.stringify(deployedFacets, null, 2)); + } + return deployedFacets; } async function getFacetCuts( - l1Rpc: string, - zkSyncAddress: string, - gettersAddress: string, - mailboxAddress: string, - executorAddress: string, - adminAddress: string, - file?: string + l1Rpc: string, + zkSyncAddress: string, + gettersAddress: string, + mailboxAddress: string, + executorAddress: string, + adminAddress: string, + file?: string ) { - const provider = new ethers.providers.JsonRpcProvider(l1Rpc); - // It's required to send read-only requests to the provider. So, we don't care about the privatekey. - const wallet = ethers.Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); + const provider = new ethers.providers.JsonRpcProvider(l1Rpc); + // It's required to send read-only requests to the provider. So, we don't care about the privatekey. + const wallet = ethers.Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); - const facetCuts = await getFacetCutsForUpgrade( - wallet, - zkSyncAddress, - adminAddress, - gettersAddress, - mailboxAddress, - executorAddress - ); - console.log(JSON.stringify(facetCuts, null, 2)); - if (file) { - fs.writeFileSync(file, JSON.stringify(facetCuts, null, 2)); - } + const facetCuts = await getFacetCutsForUpgrade( + wallet, + zkSyncAddress, + adminAddress, + gettersAddress, + mailboxAddress, + executorAddress + ); + console.log(JSON.stringify(facetCuts, null, 2)); + if (file) { + fs.writeFileSync(file, JSON.stringify(facetCuts, null, 2)); + } } -export const command = new Command('facets').description('Facets related commands'); +export const command = new Command("facets").description("Facets related commands"); command - .command('generate-facet-cuts') - .option('--l1Rpc ') - .option('--file ') - .requiredOption('--zkSyncAddress ') - .option('--getters-address ') - .option('--mailbox-address ') - .option('--executor-address ') - .option('--admin-address ') - .description('get facet cuts for upgrade') - .action(async (cmd) => { - const l1Rpc = cmd.l1Rpc ?? web3Url(); - await getFacetCuts( - l1Rpc, - cmd.zkSyncAddress, - cmd.gettersAddress, - cmd.mailboxAddress, - cmd.executorAddress, - cmd.adminAddress, - cmd.file - ); - }); + .command("generate-facet-cuts") + .option("--l1Rpc ") + .option("--file ") + .requiredOption("--zkSyncAddress ") + .option("--getters-address ") + .option("--mailbox-address ") + .option("--executor-address ") + .option("--admin-address ") + .description("get facet cuts for upgrade") + .action(async (cmd) => { + const l1Rpc = cmd.l1Rpc ?? web3Url(); + await getFacetCuts( + l1Rpc, + cmd.zkSyncAddress, + cmd.gettersAddress, + cmd.mailboxAddress, + cmd.executorAddress, + cmd.adminAddress, + cmd.file + ); + }); command - .command('deploy') - .option('--l1Rpc ') - .option('--privateKey ') - .option('--create2-address ') - .option('--file ') - .option('--nonce ') - .option('--gasPrice ') - .option('--create2-salt ') - .option('--executor') - .option('--admin') - .option('--getters') - .option('--mailbox') - .description('deploy facet cuts') - .action(async (cmd) => { - const l1Rpc = cmd.l1Rpc ?? web3Url(); - const facetsToDeploy = []; - if (cmd.executor) { - facetsToDeploy.push('ExecutorFacet'); - } - if (cmd.admin) { - facetsToDeploy.push('AdminFacet'); - } - if (cmd.getters) { - facetsToDeploy.push('GettersFacet'); - } - if (cmd.mailbox) { - facetsToDeploy.push('MailboxFacet'); - } - await deployFacetCuts( - l1Rpc, - facetsToDeploy, - cmd.create2Address, - cmd.file, - cmd.privateKey, - cmd.nonce, - cmd.gasPrice, - cmd.create2Salt - ); - }); + .command("deploy") + .option("--l1Rpc ") + .option("--privateKey ") + .option("--create2-address ") + .option("--file ") + .option("--nonce ") + .option("--gasPrice ") + .option("--create2-salt ") + .option("--executor") + .option("--admin") + .option("--getters") + .option("--mailbox") + .description("deploy facet cuts") + .action(async (cmd) => { + const l1Rpc = cmd.l1Rpc ?? web3Url(); + const facetsToDeploy = []; + if (cmd.executor) { + facetsToDeploy.push("ExecutorFacet"); + } + if (cmd.admin) { + facetsToDeploy.push("AdminFacet"); + } + if (cmd.getters) { + facetsToDeploy.push("GettersFacet"); + } + if (cmd.mailbox) { + facetsToDeploy.push("MailboxFacet"); + } + await deployFacetCuts( + l1Rpc, + facetsToDeploy, + cmd.create2Address, + cmd.file, + cmd.privateKey, + cmd.nonce, + cmd.gasPrice, + cmd.create2Salt + ); + }); diff --git a/l1-contracts/upgrade-system/index.ts b/l1-contracts/upgrade-system/index.ts index 465917497..fe05b8a23 100644 --- a/l1-contracts/upgrade-system/index.ts +++ b/l1-contracts/upgrade-system/index.ts @@ -1,25 +1,25 @@ -import { program } from 'commander'; -import { command as facetCuts } from './facets'; -import { command as verifier } from './verifier'; +import { program } from "commander"; +import { command as facetCuts } from "./facets"; +import { command as verifier } from "./verifier"; const COMMANDS = [facetCuts, verifier]; async function main() { - const ZKSYNC_HOME = process.env.ZKSYNC_HOME; + const ZKSYNC_HOME = process.env.ZKSYNC_HOME; - if (!ZKSYNC_HOME) { - throw new Error('Please set $ZKSYNC_HOME to the root of zkSync repo!'); - } else { - process.chdir(ZKSYNC_HOME); - } - program.version('0.1.0').name('upgrade-system').description('set of tools for upgrade l1 part of the system'); + if (!ZKSYNC_HOME) { + throw new Error("Please set $ZKSYNC_HOME to the root of zkSync repo!"); + } else { + process.chdir(ZKSYNC_HOME); + } + program.version("0.1.0").name("upgrade-system").description("set of tools for upgrade l1 part of the system"); - for (const command of COMMANDS) { - program.addCommand(command); - } - await program.parseAsync(process.argv); + for (const command of COMMANDS) { + program.addCommand(command); + } + await program.parseAsync(process.argv); } main().catch((err: Error) => { - console.error('Error:', err.message || err); - process.exitCode = 1; + console.error("Error:", err.message || err); + process.exitCode = 1; }); diff --git a/l1-contracts/upgrade-system/utils.ts b/l1-contracts/upgrade-system/utils.ts index a8732764e..8554e1898 100644 --- a/l1-contracts/upgrade-system/utils.ts +++ b/l1-contracts/upgrade-system/utils.ts @@ -1,19 +1,19 @@ /// @dev This method checks if the overrides contain a gasPrice (or maxFeePerGas), if not it will insert /// the maxFeePerGas -import type { ethers } from 'ethers'; +import type { ethers } from "ethers"; export async function insertGasPrice(l1Provider: ethers.providers.Provider, overrides: ethers.PayableOverrides) { - if (!overrides.gasPrice && !overrides.maxFeePerGas) { - const l1FeeData = await l1Provider.getFeeData(); + if (!overrides.gasPrice && !overrides.maxFeePerGas) { + const l1FeeData = await l1Provider.getFeeData(); - // Sometimes baseFeePerGas is not available, so we use gasPrice instead. - const baseFee = l1FeeData.lastBaseFeePerGas || l1FeeData.gasPrice; + // Sometimes baseFeePerGas is not available, so we use gasPrice instead. + const baseFee = l1FeeData.lastBaseFeePerGas || l1FeeData.gasPrice; - // ethers.js by default uses multiplcation by 2, but since the price for the L2 part - // will depend on the L1 part, doubling base fee is typically too much. - const maxFeePerGas = baseFee.mul(3).div(2).add(l1FeeData.maxPriorityFeePerGas); + // ethers.js by default uses multiplcation by 2, but since the price for the L2 part + // will depend on the L1 part, doubling base fee is typically too much. + const maxFeePerGas = baseFee.mul(3).div(2).add(l1FeeData.maxPriorityFeePerGas); - overrides.maxFeePerGas = maxFeePerGas; - overrides.maxPriorityFeePerGas = l1FeeData.maxPriorityFeePerGas; - } + overrides.maxFeePerGas = maxFeePerGas; + overrides.maxPriorityFeePerGas = l1FeeData.maxPriorityFeePerGas; + } } diff --git a/l1-contracts/upgrade-system/verifier.ts b/l1-contracts/upgrade-system/verifier.ts index 85d3dcfb7..21dd8f763 100644 --- a/l1-contracts/upgrade-system/verifier.ts +++ b/l1-contracts/upgrade-system/verifier.ts @@ -1,80 +1,72 @@ -import { Command } from 'commander'; -import type { BigNumber } from 'ethers'; -import { ethers } from 'ethers'; -import * as fs from 'fs'; -import { deployViaCreate2 } from '../src.ts/deploy-utils'; -import { web3Url } from 'zk/build/utils'; -import * as path from 'path'; -import { insertGasPrice } from './utils'; +import { Command } from "commander"; +import type { BigNumber } from "ethers"; +import { ethers } from "ethers"; +import * as fs from "fs"; +import { deployViaCreate2 } from "../src.ts/deploy-utils"; +import { web3Url } from "zk/build/utils"; +import * as path from "path"; +import { insertGasPrice } from "./utils"; -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function deployVerifier( - l1Rpc: string, - create2Address: string, - nonce?: number, - gasPrice?: BigNumber, - privateKey?: string, - file?: string, - create2Salt?: string + l1Rpc: string, + create2Address: string, + nonce?: number, + gasPrice?: BigNumber, + privateKey?: string, + file?: string, + create2Salt?: string ) { - const provider = new ethers.providers.JsonRpcProvider(l1Rpc); - const wallet = privateKey - ? new ethers.Wallet(privateKey, provider) - : ethers.Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); + const provider = new ethers.providers.JsonRpcProvider(l1Rpc); + const wallet = privateKey + ? new ethers.Wallet(privateKey, provider) + : ethers.Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); - create2Salt = create2Salt ?? ethers.constants.HashZero; + create2Salt = create2Salt ?? ethers.constants.HashZero; - const ethTxOptions = {}; - if (!nonce) { - ethTxOptions['nonce'] = await wallet.getTransactionCount(); - } - if (!gasPrice) { - await insertGasPrice(provider, ethTxOptions); - } - ethTxOptions['gasLimit'] = 10_000_000; - const [address, txHash] = await deployViaCreate2( - wallet, - 'Verifier', - [], - create2Salt, - ethTxOptions, - create2Address, - true - ); + const ethTxOptions = {}; + if (!nonce) { + ethTxOptions["nonce"] = await wallet.getTransactionCount(); + } + if (!gasPrice) { + await insertGasPrice(provider, ethTxOptions); + } + ethTxOptions["gasLimit"] = 10_000_000; + const [address, txHash] = await deployViaCreate2( + wallet, + "Verifier", + [], + create2Salt, + ethTxOptions, + create2Address, + true + ); - console.log(JSON.stringify({ address, txHash }, null, 2)); - if (file) { - fs.writeFileSync(file, JSON.stringify({ address, txHash }, null, 2)); - } - return [address, txHash]; + console.log(JSON.stringify({ address, txHash }, null, 2)); + if (file) { + fs.writeFileSync(file, JSON.stringify({ address, txHash }, null, 2)); + } + return [address, txHash]; } -export const command = new Command('verifier').description('Verifier commands'); +export const command = new Command("verifier").description("Verifier commands"); command - .command('deploy') - .option('--l1rpc ') - .option('--private-key ') - .option('--create2-address ') - .option('--file ') - .option('--nonce ') - .option('--gas-price ') - .option('--create2-salt ') - .description('deploy verifier') - .action(async (cmd) => { - const l1Rpc = cmd.l1Rpc ?? web3Url(); - await deployVerifier( - l1Rpc, - cmd.create2Address, - cmd.nonce, - cmd.gasPrice, - cmd.privateKey, - cmd.file, - cmd.create2Salt - ); - }); + .command("deploy") + .option("--l1rpc ") + .option("--private-key ") + .option("--create2-address ") + .option("--file ") + .option("--nonce ") + .option("--gas-price ") + .option("--create2-salt ") + .description("deploy verifier") + .action(async (cmd) => { + const l1Rpc = cmd.l1Rpc ?? web3Url(); + await deployVerifier(l1Rpc, cmd.create2Address, cmd.nonce, cmd.gasPrice, cmd.privateKey, cmd.file, cmd.create2Salt); + }); diff --git a/l2-contracts/hardhat.config.ts b/l2-contracts/hardhat.config.ts index 6589dad10..6c7a073bc 100644 --- a/l2-contracts/hardhat.config.ts +++ b/l2-contracts/hardhat.config.ts @@ -1,48 +1,48 @@ -import '@matterlabs/hardhat-zksync-solc'; -import '@matterlabs/hardhat-zksync-verify'; -import '@nomicfoundation/hardhat-chai-matchers'; -import '@nomiclabs/hardhat-ethers'; -import '@nomiclabs/hardhat-solpp'; -import 'hardhat-typechain'; +import "@matterlabs/hardhat-zksync-solc"; +import "@matterlabs/hardhat-zksync-verify"; +import "@nomicfoundation/hardhat-chai-matchers"; +import "@nomiclabs/hardhat-ethers"; +import "@nomiclabs/hardhat-solpp"; +import "hardhat-typechain"; // If no network is specified, use the default config if (!process.env.CHAIN_ETH_NETWORK) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - require('dotenv').config(); + // eslint-disable-next-line @typescript-eslint/no-var-requires + require("dotenv").config(); } export default { - zksolc: { - version: '1.3.14', - compilerSource: 'binary', - settings: { - isSystem: true - } + zksolc: { + version: "1.3.14", + compilerSource: "binary", + settings: { + isSystem: true, }, - solidity: { - version: '0.8.20' + }, + solidity: { + version: "0.8.20", + }, + defaultNetwork: "localhost", + networks: { + localhost: { + // era-test-node default url + url: "http://127.0.0.1:8011", + ethNetwork: null, + zksync: true, }, - defaultNetwork: 'localhost', - networks: { - localhost: { - // era-test-node default url - url: 'http://127.0.0.1:8011', - ethNetwork: null, - zksync: true - }, - zkSyncTestnet: { - url: 'https://zksync2-testnet.zksync.dev', - ethNetwork: 'goerli', - zksync: true, - // contract verification endpoint - verifyURL: 'https://zksync2-testnet-explorer.zksync.dev/contract_verification' - }, - zksyncMainnet: { - url: 'https://mainnet.era.zksync.io', - ethNetwork: 'mainnet', - zksync: true, - // contract verification endpoint - verifyURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification' - } - } + zkSyncTestnet: { + url: "https://zksync2-testnet.zksync.dev", + ethNetwork: "goerli", + zksync: true, + // contract verification endpoint + verifyURL: "https://zksync2-testnet-explorer.zksync.dev/contract_verification", + }, + zksyncMainnet: { + url: "https://mainnet.era.zksync.io", + ethNetwork: "mainnet", + zksync: true, + // contract verification endpoint + verifyURL: "https://zksync2-mainnet-explorer.zksync.io/contract_verification", + }, + }, }; diff --git a/l2-contracts/src/deployForceDeployUpgrader.ts b/l2-contracts/src/deployForceDeployUpgrader.ts index bab027f24..2d172075a 100644 --- a/l2-contracts/src/deployForceDeployUpgrader.ts +++ b/l2-contracts/src/deployForceDeployUpgrader.ts @@ -1,58 +1,58 @@ -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from './utils'; -import { web3Provider } from '../../l1-contracts/scripts/utils'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as hre from 'hardhat'; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from "./utils"; +import { web3Provider } from "../../l1-contracts/scripts/utils"; +import * as fs from "fs"; +import * as path from "path"; +import * as hre from "hardhat"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); -const priorityTxMaxGasLimit = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); +const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); // Script to deploy the force deploy upgrader contract and output its address. // Note, that this script expects that the L2 contracts have been compiled PRIOR // to running this script. async function main() { - const program = new Command(); - - program - .version('0.1.0') - .name('deploy-force-deploy-upgrader') - .description('Deploys the force deploy upgrader contract to L2'); - - program.option('--private-key ').action(async (cmd: Command) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const forceDeployUpgraderBytecode = hre.artifacts.readArtifactSync('ForceDeployUpgrader').bytecode; - const create2Salt = ethers.constants.HashZero; - const forceDeployUpgraderAddress = computeL2Create2Address( - deployWallet, - forceDeployUpgraderBytecode, - '0x', - create2Salt - ); - - // TODO: request from API how many L2 gas needs for the transaction. - await create2DeployFromL1(deployWallet, forceDeployUpgraderBytecode, '0x', create2Salt, priorityTxMaxGasLimit); - - console.log(`CONTRACTS_L2_DEFAULT_UPGRADE_ADDR=${forceDeployUpgraderAddress}`); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program + .version("0.1.0") + .name("deploy-force-deploy-upgrader") + .description("Deploys the force deploy upgrader contract to L2"); + + program.option("--private-key ").action(async (cmd: Command) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const forceDeployUpgraderBytecode = hre.artifacts.readArtifactSync("ForceDeployUpgrader").bytecode; + const create2Salt = ethers.constants.HashZero; + const forceDeployUpgraderAddress = computeL2Create2Address( + deployWallet, + forceDeployUpgraderBytecode, + "0x", + create2Salt + ); + + // TODO: request from API how many L2 gas needs for the transaction. + await create2DeployFromL1(deployWallet, forceDeployUpgraderBytecode, "0x", create2Salt, priorityTxMaxGasLimit); + + console.log(`CONTRACTS_L2_DEFAULT_UPGRADE_ADDR=${forceDeployUpgraderAddress}`); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/deployL2Weth.ts b/l2-contracts/src/deployL2Weth.ts index 8632d0f75..ef0a741bd 100644 --- a/l2-contracts/src/deployL2Weth.ts +++ b/l2-contracts/src/deployL2Weth.ts @@ -1,151 +1,138 @@ -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import { getTokens, web3Provider } from '../../l1-contracts/scripts/utils'; -import { Deployer } from '../../l1-contracts/src.ts/deploy'; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { formatUnits, parseUnits } from "ethers/lib/utils"; +import { getTokens, web3Provider } from "../../l1-contracts/scripts/utils"; +import { Deployer } from "../../l1-contracts/src.ts/deploy"; -import { applyL1ToL2Alias, computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from './utils'; +import { applyL1ToL2Alias, computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from "./utils"; -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from "fs"; +import * as path from "path"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); -const contractArtifactsPath = path.join( - process.env.ZKSYNC_HOME as string, - 'era-contracts-lambda/l2-contracts/artifacts-zk/' -); -const l2BridgeArtifactsPath = path.join(contractArtifactsPath, 'cache-zk/solpp-generated-contracts/bridge/'); +const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "era-contracts-lambda/l2-contracts/artifacts-zk/"); +const l2BridgeArtifactsPath = path.join(contractArtifactsPath, "cache-zk/solpp-generated-contracts/bridge/"); const openzeppelinTransparentProxyArtifactsPath = path.join( - contractArtifactsPath, - '@openzeppelin/contracts/proxy/transparent/' + contractArtifactsPath, + "@openzeppelin/contracts/proxy/transparent/" ); function readBytecode(path: string, fileName: string) { - return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: 'utf-8' })).bytecode; + return JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).bytecode; } function readInterface(path: string, fileName: string) { - const abi = JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: 'utf-8' })).abi; - return new ethers.utils.Interface(abi); + const abi = JSON.parse(fs.readFileSync(`${path}/${fileName}.sol/${fileName}.json`, { encoding: "utf-8" })).abi; + return new ethers.utils.Interface(abi); } -const L2_WETH_INTERFACE = readInterface(l2BridgeArtifactsPath, 'L2Weth'); -const L2_WETH_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, 'L2Weth'); -const L2_WETH_PROXY_BYTECODE = readBytecode(openzeppelinTransparentProxyArtifactsPath, 'TransparentUpgradeableProxy'); -const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || 'localhost'); -const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == 'WETH')!.address; +const L2_WETH_INTERFACE = readInterface(l2BridgeArtifactsPath, "L2Weth"); +const L2_WETH_IMPLEMENTATION_BYTECODE = readBytecode(l2BridgeArtifactsPath, "L2Weth"); +const L2_WETH_PROXY_BYTECODE = readBytecode(openzeppelinTransparentProxyArtifactsPath, "TransparentUpgradeableProxy"); +const tokens = getTokens(process.env.CHAIN_ETH_NETWORK || "localhost"); +const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; async function main() { - const program = new Command(); - - program.version('0.1.0').name('deploy-l2-weth'); - - program - .option('--private-key ') - .option('--gas-price ') - .option('--nonce ') - .action(async (cmd) => { - if (!l1WethToken) { - // Makes no sense to deploy the Rollup WETH if there is no base Layer WETH provided - console.log('Base Layer WETH address not provided so WETH deployment will be skipped.'); - return; - } - - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/0" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, 'gwei') : await provider.getGasPrice(); - console.log(`Using gas price: ${formatUnits(gasPrice, 'gwei')} gwei`); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); - console.log(`Using initial nonce: ${nonce}`); - - const deployer = new Deployer({ - deployWallet, - verbose: true - }); - - const zkSync = deployer.zkSyncContract(deployWallet); - - const priorityTxMaxGasLimit = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); - const l1GovernorAddress = await zkSync.getGovernor(); - // Check whether governor is a smart contract on L1 to apply alias if needed. - const l1GovernorCodeSize = ethers.utils.hexDataLength( - await deployWallet.provider.getCode(l1GovernorAddress) - ); - const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); - - const abiCoder = new ethers.utils.AbiCoder(); - - const l2WethImplAddr = computeL2Create2Address( - deployWallet, - L2_WETH_IMPLEMENTATION_BYTECODE, - '0x', - ethers.constants.HashZero - ); - - const proxyInitializationParams = L2_WETH_INTERFACE.encodeFunctionData('initialize', [ - 'Wrapped Ether', - 'WETH' - ]); - const l2ERC20BridgeProxyConstructor = ethers.utils.arrayify( - abiCoder.encode( - ['address', 'address', 'bytes'], - [l2WethImplAddr, l2GovernorAddress, proxyInitializationParams] - ) - ); - const l2WethProxyAddr = computeL2Create2Address( - deployWallet, - L2_WETH_PROXY_BYTECODE, - l2ERC20BridgeProxyConstructor, - ethers.constants.HashZero - ); - - const tx = await create2DeployFromL1( - deployWallet, - L2_WETH_IMPLEMENTATION_BYTECODE, - '0x', - ethers.constants.HashZero, - priorityTxMaxGasLimit - ); - console.log( - `WETH implementation transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...` - ); - - await tx.wait(); - - const tx2 = await create2DeployFromL1( - deployWallet, - L2_WETH_PROXY_BYTECODE, - l2ERC20BridgeProxyConstructor, - ethers.constants.HashZero, - priorityTxMaxGasLimit - ); - console.log( - `WETH proxy transaction sent with hash ${tx2.hash} and nonce ${tx2.nonce}. Waiting for receipt...` - ); - - await tx2.wait(); - - console.log(`CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR=${l2WethImplAddr}`); - console.log(`CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR=${l2WethProxyAddr}`); - }); - - await program.parseAsync(process.argv); + const program = new Command(); + + program.version("0.1.0").name("deploy-l2-weth"); + + program + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .action(async (cmd) => { + if (!l1WethToken) { + // Makes no sense to deploy the Rollup WETH if there is no base Layer WETH provided + console.log("Base Layer WETH address not provided so WETH deployment will be skipped."); + return; + } + + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/0" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : await provider.getGasPrice(); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using initial nonce: ${nonce}`); + + const deployer = new Deployer({ + deployWallet, + verbose: true, + }); + + const zkSync = deployer.zkSyncContract(deployWallet); + + const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); + const l1GovernorAddress = await zkSync.getGovernor(); + // Check whether governor is a smart contract on L1 to apply alias if needed. + const l1GovernorCodeSize = ethers.utils.hexDataLength(await deployWallet.provider.getCode(l1GovernorAddress)); + const l2GovernorAddress = l1GovernorCodeSize == 0 ? l1GovernorAddress : applyL1ToL2Alias(l1GovernorAddress); + + const abiCoder = new ethers.utils.AbiCoder(); + + const l2WethImplAddr = computeL2Create2Address( + deployWallet, + L2_WETH_IMPLEMENTATION_BYTECODE, + "0x", + ethers.constants.HashZero + ); + + const proxyInitializationParams = L2_WETH_INTERFACE.encodeFunctionData("initialize", ["Wrapped Ether", "WETH"]); + const l2ERC20BridgeProxyConstructor = ethers.utils.arrayify( + abiCoder.encode(["address", "address", "bytes"], [l2WethImplAddr, l2GovernorAddress, proxyInitializationParams]) + ); + const l2WethProxyAddr = computeL2Create2Address( + deployWallet, + L2_WETH_PROXY_BYTECODE, + l2ERC20BridgeProxyConstructor, + ethers.constants.HashZero + ); + + const tx = await create2DeployFromL1( + deployWallet, + L2_WETH_IMPLEMENTATION_BYTECODE, + "0x", + ethers.constants.HashZero, + priorityTxMaxGasLimit + ); + console.log( + `WETH implementation transaction sent with hash ${tx.hash} and nonce ${tx.nonce}. Waiting for receipt...` + ); + + await tx.wait(); + + const tx2 = await create2DeployFromL1( + deployWallet, + L2_WETH_PROXY_BYTECODE, + l2ERC20BridgeProxyConstructor, + ethers.constants.HashZero, + priorityTxMaxGasLimit + ); + console.log(`WETH proxy transaction sent with hash ${tx2.hash} and nonce ${tx2.nonce}. Waiting for receipt...`); + + await tx2.wait(); + + console.log(`CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR=${l2WethImplAddr}`); + console.log(`CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR=${l2WethProxyAddr}`); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/deployTestnetPaymaster.ts b/l2-contracts/src/deployTestnetPaymaster.ts index 2ddf71216..59b67060a 100644 --- a/l2-contracts/src/deployTestnetPaymaster.ts +++ b/l2-contracts/src/deployTestnetPaymaster.ts @@ -1,52 +1,52 @@ -import { Command } from 'commander'; -import { ethers, Wallet } from 'ethers'; -import { computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from './utils'; -import { web3Provider } from '../../l1-contracts/scripts/utils'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as hre from 'hardhat'; +import { Command } from "commander"; +import { ethers, Wallet } from "ethers"; +import { computeL2Create2Address, create2DeployFromL1, getNumberFromEnv } from "./utils"; +import { web3Provider } from "../../l1-contracts/scripts/utils"; +import * as fs from "fs"; +import * as path from "path"; +import * as hre from "hardhat"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); -const priorityTxMaxGasLimit = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); +const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); // Script to deploy the testnet paymaster and output its address. // Note, that this script expects that the L2 contracts have been compiled PRIOR // to running this script. async function main() { - const program = new Command(); + const program = new Command(); - program.version('0.1.0').name('deploy-testnet-paymaster').description('Deploys the testnet paymaster to L2'); + program.version("0.1.0").name("deploy-testnet-paymaster").description("Deploys the testnet paymaster to L2"); - program.option('--private-key ').action(async (cmd: Command) => { - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); + program.option("--private-key ").action(async (cmd: Command) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); - const testnetPaymasterBytecode = hre.artifacts.readArtifactSync('TestnetPaymaster').bytecode; - const create2Salt = ethers.constants.HashZero; - const paymasterAddress = computeL2Create2Address(deployWallet, testnetPaymasterBytecode, '0x', create2Salt); + const testnetPaymasterBytecode = hre.artifacts.readArtifactSync("TestnetPaymaster").bytecode; + const create2Salt = ethers.constants.HashZero; + const paymasterAddress = computeL2Create2Address(deployWallet, testnetPaymasterBytecode, "0x", create2Salt); - // TODO: request from API how many L2 gas needs for the transaction. - await ( - await create2DeployFromL1(deployWallet, testnetPaymasterBytecode, '0x', create2Salt, priorityTxMaxGasLimit) - ).wait(); + // TODO: request from API how many L2 gas needs for the transaction. + await ( + await create2DeployFromL1(deployWallet, testnetPaymasterBytecode, "0x", create2Salt, priorityTxMaxGasLimit) + ).wait(); - console.log(`CONTRACTS_L2_TESTNET_PAYMASTER_ADDR=${paymasterAddress}`); - }); + console.log(`CONTRACTS_L2_TESTNET_PAYMASTER_ADDR=${paymasterAddress}`); + }); - await program.parseAsync(process.argv); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/publish-bridge-preimages.ts b/l2-contracts/src/publish-bridge-preimages.ts index 162013519..aa0aa6f6e 100644 --- a/l2-contracts/src/publish-bridge-preimages.ts +++ b/l2-contracts/src/publish-bridge-preimages.ts @@ -1,67 +1,67 @@ -import { Command } from 'commander'; -import { Wallet, ethers } from 'ethers'; -import * as fs from 'fs'; -import { Deployer } from '../../l1-contracts/src.ts/deploy'; -import * as path from 'path'; -import { getNumberFromEnv, web3Provider } from '../../l1-contracts/scripts/utils'; -import * as hre from 'hardhat'; -import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA } from './utils'; +import { Command } from "commander"; +import { Wallet, ethers } from "ethers"; +import * as fs from "fs"; +import { Deployer } from "../../l1-contracts/src.ts/deploy"; +import * as path from "path"; +import { getNumberFromEnv, web3Provider } from "../../l1-contracts/scripts/utils"; +import * as hre from "hardhat"; +import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA } from "./utils"; -const PRIORITY_TX_MAX_GAS_LIMIT = getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT'); +const PRIORITY_TX_MAX_GAS_LIMIT = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); function getContractBytecode(contractName: string) { - return hre.artifacts.readArtifactSync(contractName).bytecode; + return hre.artifacts.readArtifactSync(contractName).bytecode; } async function main() { - const program = new Command(); + const program = new Command(); - program.version('0.1.0').name('publish-bridge-preimages'); + program.version("0.1.0").name("publish-bridge-preimages"); - program - .option('--private-key ') - .option('--nonce ') - .option('--gas-price ') - .action(async (cmd) => { - const wallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using wallet: ${wallet.address}`); + program + .option("--private-key ") + .option("--nonce ") + .option("--gas-price ") + .action(async (cmd) => { + const wallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using wallet: ${wallet.address}`); - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await wallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await wallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); - const gasPrice = cmd.gasPrice ? parseInt(cmd.gasPrice) : await wallet.getGasPrice(); - console.log(`Using gas price: ${gasPrice}`); + const gasPrice = cmd.gasPrice ? parseInt(cmd.gasPrice) : await wallet.getGasPrice(); + console.log(`Using gas price: ${gasPrice}`); - const deployer = new Deployer({ deployWallet: wallet }); - const zkSync = deployer.zkSyncContract(wallet); + const deployer = new Deployer({ deployWallet: wallet }); + const zkSync = deployer.zkSyncContract(wallet); - const publishL2ERC20BridgeTx = await zkSync.requestL2Transaction( - ethers.constants.AddressZero, - 0, - '0x', - PRIORITY_TX_MAX_GAS_LIMIT, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [getContractBytecode('L2ERC20Bridge')], - wallet.address, - { nonce, gasPrice } - ); - await publishL2ERC20BridgeTx.wait(); - }); + const publishL2ERC20BridgeTx = await zkSync.requestL2Transaction( + ethers.constants.AddressZero, + 0, + "0x", + PRIORITY_TX_MAX_GAS_LIMIT, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + [getContractBytecode("L2ERC20Bridge")], + wallet.address, + { nonce, gasPrice } + ); + await publishL2ERC20BridgeTx.wait(); + }); - await program.parseAsync(process.argv); + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/upgradeL2BridgeImpl.ts b/l2-contracts/src/upgradeL2BridgeImpl.ts index b7da81500..cdde97e13 100644 --- a/l2-contracts/src/upgradeL2BridgeImpl.ts +++ b/l2-contracts/src/upgradeL2BridgeImpl.ts @@ -1,342 +1,328 @@ -import '@nomiclabs/hardhat-ethers'; -import { Command } from 'commander'; -import { BigNumber, Wallet, ethers } from 'ethers'; -import * as fs from 'fs'; -import * as hre from 'hardhat'; -import * as path from 'path'; -import { Provider } from 'zksync-web3'; -import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from 'zksync-web3/build/src/utils'; -import { getAddressFromEnv, getNumberFromEnv, web3Provider } from '../../l1-contracts/scripts/utils'; -import { Deployer } from '../../l1-contracts/src.ts/deploy'; -import { awaitPriorityOps, computeL2Create2Address, create2DeployFromL1, getL1TxInfo } from './utils'; +import "@nomiclabs/hardhat-ethers"; +import { Command } from "commander"; +import { BigNumber, Wallet, ethers } from "ethers"; +import * as fs from "fs"; +import * as hre from "hardhat"; +import * as path from "path"; +import { Provider } from "zksync-web3"; +import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT } from "zksync-web3/build/src/utils"; +import { getAddressFromEnv, getNumberFromEnv, web3Provider } from "../../l1-contracts/scripts/utils"; +import { Deployer } from "../../l1-contracts/src.ts/deploy"; +import { awaitPriorityOps, computeL2Create2Address, create2DeployFromL1, getL1TxInfo } from "./utils"; export function getContractBytecode(contractName: string) { - return hre.artifacts.readArtifactSync(contractName).bytecode; + return hre.artifacts.readArtifactSync(contractName).bytecode; } -type SupportedContracts = 'L2ERC20Bridge' | 'L2StandardERC20' | 'L2WethBridge' | 'L2Weth'; +type SupportedContracts = "L2ERC20Bridge" | "L2StandardERC20" | "L2WethBridge" | "L2Weth"; // eslint-disable-next-line @typescript-eslint/no-explicit-any function checkSupportedContract(contract: any): contract is SupportedContracts { - if (!['L2ERC20Bridge', 'L2StandardERC20', 'L2WethBridge', 'L2Weth'].includes(contract)) { - throw new Error(`Unsupported contract: ${contract}`); - } + if (!["L2ERC20Bridge", "L2StandardERC20", "L2WethBridge", "L2Weth"].includes(contract)) { + throw new Error(`Unsupported contract: ${contract}`); + } - return true; + return true; } -const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv('CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT')); -const l2Erc20BridgeProxyAddress = getAddressFromEnv('CONTRACTS_L2_ERC20_BRIDGE_ADDR'); -const EIP1967_IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'; +const priorityTxMaxGasLimit = BigNumber.from(getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT")); +const l2Erc20BridgeProxyAddress = getAddressFromEnv("CONTRACTS_L2_ERC20_BRIDGE_ADDR"); +const EIP1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; const provider = web3Provider(); -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, 'etc/test_config/constant'); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); +const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant"); +const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); async function getERC20BeaconAddress() { - const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const bridge = (await provider.getDefaultBridgeAddresses()).erc20L2; - const artifact = await hre.artifacts.readArtifact('L2ERC20Bridge'); - const contract = new ethers.Contract(bridge, artifact.abi, provider); + const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const bridge = (await provider.getDefaultBridgeAddresses()).erc20L2; + const artifact = await hre.artifacts.readArtifact("L2ERC20Bridge"); + const contract = new ethers.Contract(bridge, artifact.abi, provider); - return await contract.l2TokenBeacon(); + return await contract.l2TokenBeacon(); } async function getWETHAddress() { - const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const wethToken = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; - return ethers.utils.hexStripZeros(await provider.getStorageAt(wethToken, EIP1967_IMPLEMENTATION_SLOT)); + const provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const wethToken = process.env.CONTRACTS_L2_WETH_TOKEN_PROXY_ADDR; + return ethers.utils.hexStripZeros(await provider.getStorageAt(wethToken, EIP1967_IMPLEMENTATION_SLOT)); } async function getTransparentProxyUpgradeCalldata(target: string) { - const proxyArtifact = await hre.artifacts.readArtifact('TransparentUpgradeableProxy'); - const proxyInterface = new ethers.utils.Interface(proxyArtifact.abi); + const proxyArtifact = await hre.artifacts.readArtifact("TransparentUpgradeableProxy"); + const proxyInterface = new ethers.utils.Interface(proxyArtifact.abi); - return proxyInterface.encodeFunctionData('upgradeTo', [target]); + return proxyInterface.encodeFunctionData("upgradeTo", [target]); } async function getBeaconProxyUpgradeCalldata(target: string) { - const proxyArtifact = await hre.artifacts.readArtifact('UpgradeableBeacon'); - const proxyInterface = new ethers.utils.Interface(proxyArtifact.abi); + const proxyArtifact = await hre.artifacts.readArtifact("UpgradeableBeacon"); + const proxyInterface = new ethers.utils.Interface(proxyArtifact.abi); - return proxyInterface.encodeFunctionData('upgradeTo', [target]); + return proxyInterface.encodeFunctionData("upgradeTo", [target]); } async function getTransparentProxyUpgradeTxInfo( - deployer: Deployer, - target: string, - proxyAddress: string, - refundRecipient: string, - gasPrice: BigNumber + deployer: Deployer, + target: string, + proxyAddress: string, + refundRecipient: string, + gasPrice: BigNumber ) { - const l2Calldata = await getTransparentProxyUpgradeCalldata(target); - return await getL1TxInfo(deployer, proxyAddress, l2Calldata, refundRecipient, gasPrice); + const l2Calldata = await getTransparentProxyUpgradeCalldata(target); + return await getL1TxInfo(deployer, proxyAddress, l2Calldata, refundRecipient, gasPrice); } async function getTokenBeaconUpgradeTxInfo( - deployer: Deployer, - target: string, - refundRecipient: string, - gasPrice: BigNumber, - proxy: string + deployer: Deployer, + target: string, + refundRecipient: string, + gasPrice: BigNumber, + proxy: string ) { - const l2Calldata = await getBeaconProxyUpgradeCalldata(target); + const l2Calldata = await getBeaconProxyUpgradeCalldata(target); - return await getL1TxInfo(deployer, proxy, l2Calldata, refundRecipient, gasPrice, priorityTxMaxGasLimit, provider); + return await getL1TxInfo(deployer, proxy, l2Calldata, refundRecipient, gasPrice, priorityTxMaxGasLimit, provider); } async function getTxInfo( - deployer: Deployer, - target: string, - refundRecipient: string, - gasPrice: BigNumber, - contract: SupportedContracts, - l2ProxyAddress?: string + deployer: Deployer, + target: string, + refundRecipient: string, + gasPrice: BigNumber, + contract: SupportedContracts, + l2ProxyAddress?: string ) { - if (contract === 'L2ERC20Bridge') { - return getTransparentProxyUpgradeTxInfo(deployer, target, l2Erc20BridgeProxyAddress, refundRecipient, gasPrice); - } else if (contract == 'L2Weth') { - throw new Error( - 'The latest L2Weth implementation requires L2WethBridge to be deployed in order to be correctly initialized, which is not the case on the majority of networks. Remove this error once the bridge is deployed.' - ); - } else if (contract == 'L2StandardERC20') { - if (!l2ProxyAddress) { - console.log('Explicit beacon address is not supplied, requesting the one from L2 node'); - l2ProxyAddress = await getERC20BeaconAddress(); - } - console.log(`Using beacon address: ${l2ProxyAddress}`); - - return getTokenBeaconUpgradeTxInfo(deployer, target, refundRecipient, gasPrice, l2ProxyAddress); - } else { - throw new Error(`Unsupported contract: ${contract}`); + if (contract === "L2ERC20Bridge") { + return getTransparentProxyUpgradeTxInfo(deployer, target, l2Erc20BridgeProxyAddress, refundRecipient, gasPrice); + } else if (contract == "L2Weth") { + throw new Error( + "The latest L2Weth implementation requires L2WethBridge to be deployed in order to be correctly initialized, which is not the case on the majority of networks. Remove this error once the bridge is deployed." + ); + } else if (contract == "L2StandardERC20") { + if (!l2ProxyAddress) { + console.log("Explicit beacon address is not supplied, requesting the one from L2 node"); + l2ProxyAddress = await getERC20BeaconAddress(); } + console.log(`Using beacon address: ${l2ProxyAddress}`); + + return getTokenBeaconUpgradeTxInfo(deployer, target, refundRecipient, gasPrice, l2ProxyAddress); + } else { + throw new Error(`Unsupported contract: ${contract}`); + } } async function main() { - const program = new Command(); - - program.version('0.1.0').name('upgrade-l2-bridge-impl'); - - program - .command('deploy-target') - .option('--contract ') - .option('--private-key ') - .option('--gas-price ') - .option('--create2-salt ') - .option('--no-l2-double-check') - .action(async (cmd) => { - // We deploy the target contract through L1 to ensure security - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - const deployer = new Deployer({ deployWallet }); - const gasPrice = cmd.gasPrice - ? ethers.utils.parseUnits(cmd.gasPrice, 'gwei') - : (await provider.getGasPrice()).mul(3).div(2); - const salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.constants.HashZero); - checkSupportedContract(cmd.contract); - - console.log(`Using deployer wallet: ${deployWallet.address}`); - console.log('Gas price: ', ethers.utils.formatUnits(gasPrice, 'gwei')); - console.log('Salt: ', salt); - - const bridgeImplBytecode = getContractBytecode(cmd.contract); - const l2ERC20BridgeImplAddr = computeL2Create2Address(deployWallet, bridgeImplBytecode, '0x', salt); - console.log('Bridge implemenation address: ', l2ERC20BridgeImplAddr); - - if (cmd.l2DoubleCheck !== false) { - // If the bytecode has already been deployed there is no need to deploy it again. - const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - const deployedBytecode = await zksProvider.getCode(l2ERC20BridgeImplAddr); - if (deployedBytecode === bridgeImplBytecode) { - console.log('The bytecode has been already deployed!'); - console.log('Address:', l2ERC20BridgeImplAddr); - return; - } else if (ethers.utils.arrayify(deployedBytecode).length > 0) { - console.log('CREATE2 DERIVATION: A different bytecode has been deployed on that address'); - process.exit(1); - } else { - console.log('The contract has not been deployed yet. Proceeding with deployment'); - } - } - - const tx = await create2DeployFromL1( - deployWallet, - bridgeImplBytecode, - '0x', - salt, - priorityTxMaxGasLimit, - gasPrice - ); - console.log('L1 tx hash: ', tx.hash); - - const receipt = await tx.wait(); - if (receipt.status !== 1) { - console.error('L1 tx failed'); - process.exit(1); - } - - // Double checking that the deployment has been successful on L2. - // Note that it requires working L2 node. - if (cmd.l2DoubleCheck !== false) { - console.log('Waiting for the L2 transaction to be committed...'); - const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - await awaitPriorityOps(zksProvider, receipt, deployer.zkSyncContract(deployWallet).interface); - - // Double checking that the bridge implementation has been deployed - const deployedBytecode = await zksProvider.getCode(l2ERC20BridgeImplAddr); - if (deployedBytecode != bridgeImplBytecode) { - console.error('Bridge implementation has not been deployed'); - process.exit(1); - } else { - console.log('Transaction has been successfully committed'); - } - } - - console.log('\n'); - console.log('Bridge implementation has been successfuly deployed!'); - console.log('Address:', l2ERC20BridgeImplAddr); - }); - - program - .command('prepare-l1-tx-info') - .option('--contract ') - .option('--target-address ') - .option('--l2-proxy-address ') - .option('--gas-price ') - .option('--deployer-private-key ') - .option('--refund-recipient ') - .action(async (cmd) => { - const gasPrice = cmd.gasPrice - ? ethers.utils.parseUnits(cmd.gasPrice, 'gwei') - : (await provider.getGasPrice()).mul(3).div(2); - const deployWallet = cmd.deployerPrivateKey - ? new Wallet(cmd.deployerPrivateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - const deployer = new Deployer({ deployWallet }); - const target = cmd.targetAddress as string; - if (!target) { - throw new Error('L2 target address is not provided'); - } - checkSupportedContract(cmd.contract); - - const refundRecipient = cmd.refundRecipient ? cmd.refundRecipient : deployWallet.address; - console.log('Gas price: ', ethers.utils.formatUnits(gasPrice, 'gwei')); - console.log('Target address: ', target); - console.log('Refund recipient: ', refundRecipient); - const txInfo = await getTxInfo( - deployer, - target, - refundRecipient, - gasPrice, - cmd.contract, - cmd.l2ProxyAddress - ); - - console.log(JSON.stringify(txInfo, null, 4)); - console.log('IMPORTANT: gasPrice that you provide in the transaction should <= to the one provided above.'); - }); - - program - .command('instant-upgrade') - .option('--contract ') - .option('--target-address ') - .option('--l2-proxy-address ') - .option('--gas-price ') - .option('--governor-private-key ') - .option('--refund-recipient ') - .option('--no-l2-double-check') - .action(async (cmd) => { - const gasPrice = cmd.gasPrice - ? ethers.utils.parseUnits(cmd.gasPrice, 'gwei') - : (await provider.getGasPrice()).mul(3).div(2); - const deployWallet = cmd.governorPrivateKey - ? new Wallet(cmd.governorPrivateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - const deployer = new Deployer({ deployWallet }); - const target = cmd.targetAddress as string; - if (!target) { - throw new Error('L2 target address is not provided'); - } - checkSupportedContract(cmd.contract); - - const refundRecipient = cmd.refundRecipient ? cmd.refundRecipient : deployWallet.address; - console.log(`Using deployer wallet: ${deployWallet.address}`); - console.log('Gas price: ', ethers.utils.formatUnits(gasPrice, 'gwei')); - console.log('Target address: ', target); - console.log('Refund recipient: ', refundRecipient); - - const txInfo = await getTxInfo( - deployer, - target, - refundRecipient, - gasPrice, - cmd.contract, - cmd.l2ProxyAddress - ); - const tx = await deployWallet.sendTransaction(txInfo); - console.log('L1 tx hash: ', tx.hash); - - const receipt = await tx.wait(); - if (receipt.status !== 1) { - console.error('L1 tx failed'); - process.exit(1); - } - - // Double checking that the upgrade has been successful on L2. - // Note that it requires working L2 node. - if (cmd.l2DoubleCheck !== false) { - const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); - await awaitPriorityOps(zksProvider, receipt, deployer.zkSyncContract(deployWallet).interface); - - console.log('The L2 transaction has been successfully committed'); - } - }); - - program.command('get-l2-erc20-beacon-address').action(async () => { - console.log(`L2 ERC20 beacon address: ${await getERC20BeaconAddress()}`); + const program = new Command(); + + program.version("0.1.0").name("upgrade-l2-bridge-impl"); + + program + .command("deploy-target") + .option("--contract ") + .option("--private-key ") + .option("--gas-price ") + .option("--create2-salt ") + .option("--no-l2-double-check") + .action(async (cmd) => { + // We deploy the target contract through L1 to ensure security + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + const deployer = new Deployer({ deployWallet }); + const gasPrice = cmd.gasPrice + ? ethers.utils.parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(3).div(2); + const salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.constants.HashZero); + checkSupportedContract(cmd.contract); + + console.log(`Using deployer wallet: ${deployWallet.address}`); + console.log("Gas price: ", ethers.utils.formatUnits(gasPrice, "gwei")); + console.log("Salt: ", salt); + + const bridgeImplBytecode = getContractBytecode(cmd.contract); + const l2ERC20BridgeImplAddr = computeL2Create2Address(deployWallet, bridgeImplBytecode, "0x", salt); + console.log("Bridge implemenation address: ", l2ERC20BridgeImplAddr); + + if (cmd.l2DoubleCheck !== false) { + // If the bytecode has already been deployed there is no need to deploy it again. + const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const deployedBytecode = await zksProvider.getCode(l2ERC20BridgeImplAddr); + if (deployedBytecode === bridgeImplBytecode) { + console.log("The bytecode has been already deployed!"); + console.log("Address:", l2ERC20BridgeImplAddr); + return; + } else if (ethers.utils.arrayify(deployedBytecode).length > 0) { + console.log("CREATE2 DERIVATION: A different bytecode has been deployed on that address"); + process.exit(1); + } else { + console.log("The contract has not been deployed yet. Proceeding with deployment"); + } + } + + const tx = await create2DeployFromL1( + deployWallet, + bridgeImplBytecode, + "0x", + salt, + priorityTxMaxGasLimit, + gasPrice + ); + console.log("L1 tx hash: ", tx.hash); + + const receipt = await tx.wait(); + if (receipt.status !== 1) { + console.error("L1 tx failed"); + process.exit(1); + } + + // Double checking that the deployment has been successful on L2. + // Note that it requires working L2 node. + if (cmd.l2DoubleCheck !== false) { + console.log("Waiting for the L2 transaction to be committed..."); + const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + await awaitPriorityOps(zksProvider, receipt, deployer.zkSyncContract(deployWallet).interface); + + // Double checking that the bridge implementation has been deployed + const deployedBytecode = await zksProvider.getCode(l2ERC20BridgeImplAddr); + if (deployedBytecode != bridgeImplBytecode) { + console.error("Bridge implementation has not been deployed"); + process.exit(1); + } else { + console.log("Transaction has been successfully committed"); + } + } + + console.log("\n"); + console.log("Bridge implementation has been successfuly deployed!"); + console.log("Address:", l2ERC20BridgeImplAddr); + }); + + program + .command("prepare-l1-tx-info") + .option("--contract ") + .option("--target-address ") + .option("--l2-proxy-address ") + .option("--gas-price ") + .option("--deployer-private-key ") + .option("--refund-recipient ") + .action(async (cmd) => { + const gasPrice = cmd.gasPrice + ? ethers.utils.parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(3).div(2); + const deployWallet = cmd.deployerPrivateKey + ? new Wallet(cmd.deployerPrivateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + const deployer = new Deployer({ deployWallet }); + const target = cmd.targetAddress as string; + if (!target) { + throw new Error("L2 target address is not provided"); + } + checkSupportedContract(cmd.contract); + + const refundRecipient = cmd.refundRecipient ? cmd.refundRecipient : deployWallet.address; + console.log("Gas price: ", ethers.utils.formatUnits(gasPrice, "gwei")); + console.log("Target address: ", target); + console.log("Refund recipient: ", refundRecipient); + const txInfo = await getTxInfo(deployer, target, refundRecipient, gasPrice, cmd.contract, cmd.l2ProxyAddress); + + console.log(JSON.stringify(txInfo, null, 4)); + console.log("IMPORTANT: gasPrice that you provide in the transaction should <= to the one provided above."); }); - program.command('get-weth-token-implementation').action(async () => { - console.log(`WETH token implementation address: ${await getWETHAddress()}`); + program + .command("instant-upgrade") + .option("--contract ") + .option("--target-address ") + .option("--l2-proxy-address ") + .option("--gas-price ") + .option("--governor-private-key ") + .option("--refund-recipient ") + .option("--no-l2-double-check") + .action(async (cmd) => { + const gasPrice = cmd.gasPrice + ? ethers.utils.parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(3).div(2); + const deployWallet = cmd.governorPrivateKey + ? new Wallet(cmd.governorPrivateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + const deployer = new Deployer({ deployWallet }); + const target = cmd.targetAddress as string; + if (!target) { + throw new Error("L2 target address is not provided"); + } + checkSupportedContract(cmd.contract); + + const refundRecipient = cmd.refundRecipient ? cmd.refundRecipient : deployWallet.address; + console.log(`Using deployer wallet: ${deployWallet.address}`); + console.log("Gas price: ", ethers.utils.formatUnits(gasPrice, "gwei")); + console.log("Target address: ", target); + console.log("Refund recipient: ", refundRecipient); + + const txInfo = await getTxInfo(deployer, target, refundRecipient, gasPrice, cmd.contract, cmd.l2ProxyAddress); + const tx = await deployWallet.sendTransaction(txInfo); + console.log("L1 tx hash: ", tx.hash); + + const receipt = await tx.wait(); + if (receipt.status !== 1) { + console.error("L1 tx failed"); + process.exit(1); + } + + // Double checking that the upgrade has been successful on L2. + // Note that it requires working L2 node. + if (cmd.l2DoubleCheck !== false) { + const zksProvider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + await awaitPriorityOps(zksProvider, receipt, deployer.zkSyncContract(deployWallet).interface); + + console.log("The L2 transaction has been successfully committed"); + } }); - program - .command('get-base-cost-for-max-op') - .option('--gas-price ') - .action(async (cmd) => { - if (!cmd.gasPrice) { - throw new Error('Gas price is not provided'); - } + program.command("get-l2-erc20-beacon-address").action(async () => { + console.log(`L2 ERC20 beacon address: ${await getERC20BeaconAddress()}`); + }); + + program.command("get-weth-token-implementation").action(async () => { + console.log(`WETH token implementation address: ${await getWETHAddress()}`); + }); - const gasPrice = ethers.utils.parseUnits(cmd.gasPrice, 'gwei'); + program + .command("get-base-cost-for-max-op") + .option("--gas-price ") + .action(async (cmd) => { + if (!cmd.gasPrice) { + throw new Error("Gas price is not provided"); + } - const deployer = new Deployer({ deployWallet: Wallet.createRandom().connect(provider) }); - const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); + const gasPrice = ethers.utils.parseUnits(cmd.gasPrice, "gwei"); - const neededValue = await zksync.l2TransactionBaseCost( - gasPrice, - priorityTxMaxGasLimit, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT - ); + const deployer = new Deployer({ deployWallet: Wallet.createRandom().connect(provider) }); + const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); - console.log(`Base cost for priority tx with max ergs: ${ethers.utils.formatEther(neededValue)} ETH`); - }); + const neededValue = await zksync.l2TransactionBaseCost( + gasPrice, + priorityTxMaxGasLimit, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT + ); - await program.parseAsync(process.argv); + console.log(`Base cost for priority tx with max ergs: ${ethers.utils.formatEther(neededValue)} ETH`); + }); + + await program.parseAsync(process.argv); } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l2-contracts/src/utils.ts b/l2-contracts/src/utils.ts index 4c0b31144..d27ce1204 100644 --- a/l2-contracts/src/utils.ts +++ b/l2-contracts/src/utils.ts @@ -1,166 +1,166 @@ -import { artifacts } from 'hardhat'; +import { artifacts } from "hardhat"; -import { Interface } from 'ethers/lib/utils'; -import type { Deployer } from '../../l1-contracts/src.ts/deploy'; -import { deployedAddressesFromEnv } from '../../l1-contracts/src.ts/deploy'; -import { IZkSyncFactory } from '../../l1-contracts/typechain/IZkSyncFactory'; +import { Interface } from "ethers/lib/utils"; +import type { Deployer } from "../../l1-contracts/src.ts/deploy"; +import { deployedAddressesFromEnv } from "../../l1-contracts/src.ts/deploy"; +import { IZkSyncFactory } from "../../l1-contracts/typechain/IZkSyncFactory"; -import type { BigNumber, BytesLike, Wallet } from 'ethers'; -import { ethers } from 'ethers'; -import type { Provider } from 'zksync-web3'; -import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, sleep } from 'zksync-web3/build/src/utils'; +import type { BigNumber, BytesLike, Wallet } from "ethers"; +import { ethers } from "ethers"; +import type { Provider } from "zksync-web3"; +import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, sleep } from "zksync-web3/build/src/utils"; // eslint-disable-next-line @typescript-eslint/no-var-requires -export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require('../../SystemConfig.json').REQUIRED_L2_GAS_PRICE_PER_PUBDATA; +export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../SystemConfig.json").REQUIRED_L2_GAS_PRICE_PER_PUBDATA; -const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000008006'; -const CREATE2_PREFIX = ethers.utils.solidityKeccak256(['string'], ['zksyncCreate2']); -const L1_TO_L2_ALIAS_OFFSET = '0x1111000000000000000000000000000000001111'; +const DEPLOYER_SYSTEM_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000008006"; +const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate2"]); +const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); export function applyL1ToL2Alias(address: string): string { - return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); + return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); } export function hashL2Bytecode(bytecode: ethers.BytesLike): Uint8Array { - // For getting the consistent length we first convert the bytecode to UInt8Array - const bytecodeAsArray = ethers.utils.arrayify(bytecode); - - if (bytecodeAsArray.length % 32 != 0) { - throw new Error('The bytecode length in bytes must be divisible by 32'); - } - - const hashStr = ethers.utils.sha256(bytecodeAsArray); - const hash = ethers.utils.arrayify(hashStr); - - // Note that the length of the bytecode - // should be provided in 32-byte words. - const bytecodeLengthInWords = bytecodeAsArray.length / 32; - if (bytecodeLengthInWords % 2 == 0) { - throw new Error('Bytecode length in 32-byte words must be odd'); - } - const bytecodeLength = ethers.utils.arrayify(bytecodeAsArray.length / 32); - if (bytecodeLength.length > 2) { - throw new Error('Bytecode length must be less than 2^16 bytes'); - } - // The bytecode should always take the first 2 bytes of the bytecode hash, - // so we pad it from the left in case the length is smaller than 2 bytes. - const bytecodeLengthPadded = ethers.utils.zeroPad(bytecodeLength, 2); - - const codeHashVersion = new Uint8Array([1, 0]); - hash.set(codeHashVersion, 0); - hash.set(bytecodeLengthPadded, 2); - - return hash; + // For getting the consistent length we first convert the bytecode to UInt8Array + const bytecodeAsArray = ethers.utils.arrayify(bytecode); + + if (bytecodeAsArray.length % 32 != 0) { + throw new Error("The bytecode length in bytes must be divisible by 32"); + } + + const hashStr = ethers.utils.sha256(bytecodeAsArray); + const hash = ethers.utils.arrayify(hashStr); + + // Note that the length of the bytecode + // should be provided in 32-byte words. + const bytecodeLengthInWords = bytecodeAsArray.length / 32; + if (bytecodeLengthInWords % 2 == 0) { + throw new Error("Bytecode length in 32-byte words must be odd"); + } + const bytecodeLength = ethers.utils.arrayify(bytecodeAsArray.length / 32); + if (bytecodeLength.length > 2) { + throw new Error("Bytecode length must be less than 2^16 bytes"); + } + // The bytecode should always take the first 2 bytes of the bytecode hash, + // so we pad it from the left in case the length is smaller than 2 bytes. + const bytecodeLengthPadded = ethers.utils.zeroPad(bytecodeLength, 2); + + const codeHashVersion = new Uint8Array([1, 0]); + hash.set(codeHashVersion, 0); + hash.set(bytecodeLengthPadded, 2); + + return hash; } export function computeL2Create2Address( - deployWallet: Wallet, - bytecode: BytesLike, - constructorInput: BytesLike, - create2Salt: BytesLike + deployWallet: Wallet, + bytecode: BytesLike, + constructorInput: BytesLike, + create2Salt: BytesLike ) { - const senderBytes = ethers.utils.hexZeroPad(deployWallet.address, 32); - const bytecodeHash = hashL2Bytecode(bytecode); - const constructorInputHash = ethers.utils.keccak256(constructorInput); + const senderBytes = ethers.utils.hexZeroPad(deployWallet.address, 32); + const bytecodeHash = hashL2Bytecode(bytecode); + const constructorInputHash = ethers.utils.keccak256(constructorInput); - const data = ethers.utils.keccak256( - ethers.utils.concat([CREATE2_PREFIX, senderBytes, create2Salt, bytecodeHash, constructorInputHash]) - ); + const data = ethers.utils.keccak256( + ethers.utils.concat([CREATE2_PREFIX, senderBytes, create2Salt, bytecodeHash, constructorInputHash]) + ); - return ethers.utils.hexDataSlice(data, 12); + return ethers.utils.hexDataSlice(data, 12); } export async function create2DeployFromL1( - wallet: ethers.Wallet, - bytecode: ethers.BytesLike, - constructor: ethers.BytesLike, - create2Salt: ethers.BytesLike, - l2GasLimit: ethers.BigNumberish, - gasPrice?: ethers.BigNumberish + wallet: ethers.Wallet, + bytecode: ethers.BytesLike, + constructor: ethers.BytesLike, + create2Salt: ethers.BytesLike, + l2GasLimit: ethers.BigNumberish, + gasPrice?: ethers.BigNumberish ) { - const zkSyncAddress = deployedAddressesFromEnv().ZkSync.DiamondProxy; - const zkSync = IZkSyncFactory.connect(zkSyncAddress, wallet); - - const deployerSystemContracts = new Interface(artifacts.readArtifactSync('IContractDeployer').abi); - const bytecodeHash = hashL2Bytecode(bytecode); - const calldata = deployerSystemContracts.encodeFunctionData('create2', [create2Salt, bytecodeHash, constructor]); - gasPrice ??= await zkSync.provider.getGasPrice(); - const expectedCost = await zkSync.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); - - return await zkSync.requestL2Transaction( - DEPLOYER_SYSTEM_CONTRACT_ADDRESS, - 0, - calldata, - l2GasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - [bytecode], - wallet.address, - { value: expectedCost, gasPrice } - ); + const zkSyncAddress = deployedAddressesFromEnv().ZkSync.DiamondProxy; + const zkSync = IZkSyncFactory.connect(zkSyncAddress, wallet); + + const deployerSystemContracts = new Interface(artifacts.readArtifactSync("IContractDeployer").abi); + const bytecodeHash = hashL2Bytecode(bytecode); + const calldata = deployerSystemContracts.encodeFunctionData("create2", [create2Salt, bytecodeHash, constructor]); + gasPrice ??= await zkSync.provider.getGasPrice(); + const expectedCost = await zkSync.l2TransactionBaseCost(gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); + + return await zkSync.requestL2Transaction( + DEPLOYER_SYSTEM_CONTRACT_ADDRESS, + 0, + calldata, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + [bytecode], + wallet.address, + { value: expectedCost, gasPrice } + ); } export function getNumberFromEnv(envName: string): string { - const number = process.env[envName]; - if (!/^([1-9]\d*|0)$/.test(number)) { - throw new Error(`Incorrect number format number in ${envName} env: ${number}`); - } - return number; + const number = process.env[envName]; + if (!/^([1-9]\d*|0)$/.test(number)) { + throw new Error(`Incorrect number format number in ${envName} env: ${number}`); + } + return number; } export async function awaitPriorityOps( - zksProvider: Provider, - l1TxReceipt: ethers.providers.TransactionReceipt, - zksyncInterface: ethers.utils.Interface + zksProvider: Provider, + l1TxReceipt: ethers.providers.TransactionReceipt, + zksyncInterface: ethers.utils.Interface ) { - const deployL2TxHashes = l1TxReceipt.logs - .map((log) => zksyncInterface.parseLog(log)) - .filter((event) => event.name === 'NewPriorityRequest') - .map((event) => event.args[1]); - for (const txHash of deployL2TxHashes) { - console.log('Awaiting L2 transaction with hash: ', txHash); - let receipt = null; - while (receipt == null) { - receipt = await zksProvider.getTransactionReceipt(txHash); - await sleep(100); - } - - if (receipt.status != 1) { - throw new Error('Failed to process L2 tx'); - } + const deployL2TxHashes = l1TxReceipt.logs + .map((log) => zksyncInterface.parseLog(log)) + .filter((event) => event.name === "NewPriorityRequest") + .map((event) => event.args[1]); + for (const txHash of deployL2TxHashes) { + console.log("Awaiting L2 transaction with hash: ", txHash); + let receipt = null; + while (receipt == null) { + receipt = await zksProvider.getTransactionReceipt(txHash); + await sleep(100); + } + + if (receipt.status != 1) { + throw new Error("Failed to process L2 tx"); } + } } export async function getL1TxInfo( - deployer: Deployer, - to: string, - l2Calldata: string, - refundRecipient: string, - gasPrice: BigNumber, - priorityTxMaxGasLimit: BigNumber, - provider: ethers.providers.JsonRpcProvider + deployer: Deployer, + to: string, + l2Calldata: string, + refundRecipient: string, + gasPrice: BigNumber, + priorityTxMaxGasLimit: BigNumber, + provider: ethers.providers.JsonRpcProvider ) { - const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); - const l1Calldata = zksync.interface.encodeFunctionData('requestL2Transaction', [ - to, - 0, - l2Calldata, - priorityTxMaxGasLimit, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, - [], // It is assumed that the target has already been deployed - refundRecipient - ]); - - const neededValue = await zksync.l2TransactionBaseCost( - gasPrice, - priorityTxMaxGasLimit, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT - ); - - return { - to: zksync.address, - data: l1Calldata, - value: neededValue.toString(), - gasPrice: gasPrice.toString() - }; + const zksync = deployer.zkSyncContract(ethers.Wallet.createRandom().connect(provider)); + const l1Calldata = zksync.interface.encodeFunctionData("requestL2Transaction", [ + to, + 0, + l2Calldata, + priorityTxMaxGasLimit, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + [], // It is assumed that the target has already been deployed + refundRecipient, + ]); + + const neededValue = await zksync.l2TransactionBaseCost( + gasPrice, + priorityTxMaxGasLimit, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT + ); + + return { + to: zksync.address, + data: l1Calldata, + value: neededValue.toString(), + gasPrice: gasPrice.toString(), + }; } diff --git a/l2-contracts/src/verify.ts b/l2-contracts/src/verify.ts index a776bf75f..65a7a0084 100644 --- a/l2-contracts/src/verify.ts +++ b/l2-contracts/src/verify.ts @@ -1,42 +1,42 @@ -import * as hardhat from 'hardhat'; +import * as hardhat from "hardhat"; // eslint-disable-next-line @typescript-eslint/no-explicit-any function verifyPromise(address: string, constructorArguments?: Array, libraries?: object): Promise { - return new Promise((resolve, reject) => { - hardhat - .run('verify:verify', { address, constructorArguments, libraries }) - .then(() => resolve(`Successfully verified ${address}`)) - .catch((e) => reject(`Failed to verify ${address}\nError: ${e.message}`)); - }); + return new Promise((resolve, reject) => { + hardhat + .run("verify:verify", { address, constructorArguments, libraries }) + .then(() => resolve(`Successfully verified ${address}`)) + .catch((e) => reject(`Failed to verify ${address}\nError: ${e.message}`)); + }); } async function main() { - if (process.env.CHAIN_ETH_NETWORK == 'localhost') { - console.log('Skip contract verification on localhost'); - return; - } + if (process.env.CHAIN_ETH_NETWORK == "localhost") { + console.log("Skip contract verification on localhost"); + return; + } - const promises = []; + const promises = []; - // Contracts without constructor parameters - for (const address of [ - process.env.CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR, - process.env.CONTRACTS_L2_ERC20_BRIDGE_IMPL_ADDR, - process.env.CONTRACTS_L2_ERC20_BRIDGE_TOKEN_IMPL_ADDR - ]) { - const promise = verifyPromise(address); - promises.push(promise); - } + // Contracts without constructor parameters + for (const address of [ + process.env.CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR, + process.env.CONTRACTS_L2_ERC20_BRIDGE_IMPL_ADDR, + process.env.CONTRACTS_L2_ERC20_BRIDGE_TOKEN_IMPL_ADDR, + ]) { + const promise = verifyPromise(address); + promises.push(promise); + } - const messages = await Promise.allSettled(promises); - for (const message of messages) { - console.log(message.status == 'fulfilled' ? message.value : message.reason); - } + const messages = await Promise.allSettled(promises); + for (const message of messages) { + console.log(message.status == "fulfilled" ? message.value : message.reason); + } } main() - .then(() => process.exit(0)) - .catch((err) => { - console.error('Error:', err.message || err); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/l2-contracts/test/weth.test.ts b/l2-contracts/test/weth.test.ts index 182e110aa..1af07d2dc 100644 --- a/l2-contracts/test/weth.test.ts +++ b/l2-contracts/test/weth.test.ts @@ -1,125 +1,125 @@ -import { Deployer } from '@matterlabs/hardhat-zksync-deploy'; -import { expect } from 'chai'; -import { ethers } from 'ethers'; -import * as hre from 'hardhat'; -import { Provider, Wallet } from 'zksync-web3'; -import type { L2Weth } from '../typechain/L2Weth'; -import type { L2WethBridge } from '../typechain/L2WethBridge'; -import { L2WethBridgeFactory } from '../typechain/L2WethBridgeFactory'; -import { L2WethFactory } from '../typechain/L2WethFactory'; +import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; +import { expect } from "chai"; +import { ethers } from "ethers"; +import * as hre from "hardhat"; +import { Provider, Wallet } from "zksync-web3"; +import type { L2Weth } from "../typechain/L2Weth"; +import type { L2WethBridge } from "../typechain/L2WethBridge"; +import { L2WethBridgeFactory } from "../typechain/L2WethBridgeFactory"; +import { L2WethFactory } from "../typechain/L2WethFactory"; const richAccount = { - address: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', - privateKey: '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110' + address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", }; -const eth18 = ethers.utils.parseEther('18'); - -describe('WETH token & WETH bridge', function () { - const provider = new Provider(hre.config.networks.localhost.url); - const wallet = new Wallet(richAccount.privateKey, provider); - let wethToken: L2Weth; - let wethBridge: L2WethBridge; - - before('Deploy token and bridge', async function () { - const deployer = new Deployer(hre, wallet); - const wethTokenImpl = await deployer.deploy(await deployer.loadArtifact('L2Weth')); - const wethBridgeImpl = await deployer.deploy(await deployer.loadArtifact('L2WethBridge')); - const randomAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); - - const wethTokenProxy = await deployer.deploy(await deployer.loadArtifact('TransparentUpgradeableProxy'), [ - wethTokenImpl.address, - randomAddress, - '0x' - ]); - const wethBridgeProxy = (await deployer.deploy(await deployer.loadArtifact('TransparentUpgradeableProxy'), [ - wethBridgeImpl.address, - randomAddress, - '0x' - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ])) as any; - - wethToken = L2WethFactory.connect(wethTokenProxy.address, wallet); - wethBridge = L2WethBridgeFactory.connect(wethBridgeProxy.address, wallet); - - await wethToken.initialize('Wrapped Ether', 'WETH'); - await wethToken.initializeV2(wethBridge.address, randomAddress); - - await wethBridge.initialize(randomAddress, randomAddress, wethToken.address); - }); - - it('Should deposit WETH by calling deposit()', async function () { - await wethToken.deposit({ value: eth18 }).then((tx) => tx.wait()); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18); - }); - - it('Should deposit WETH by sending', async function () { - await wallet - .sendTransaction({ - to: wethToken.address, - value: eth18 - }) - .then((tx) => tx.wait()); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.mul(2)); - }); - - it('Should fail depositing with random calldata', async function () { - await expect( - wallet.sendTransaction({ - data: ethers.utils.randomBytes(36), - to: wethToken.address, - value: eth18, - gasLimit: 100_000 - }) - ).to.be.reverted; - }); - - it('Should withdraw WETH to L2 ETH', async function () { - await wethToken.withdraw(eth18).then((tx) => tx.wait()); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18); - }); - - it('Should withdraw WETH to L1 ETH', async function () { - await expect(wethBridge.withdraw(wallet.address, wethToken.address, eth18.div(2))) - .to.emit(wethBridge, 'WithdrawalInitiated') - .and.to.emit(wethToken, 'BridgeBurn'); - expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.div(2)); - }); - - it('Should deposit WETH to another account', async function () { - const anotherWallet = new Wallet(ethers.utils.randomBytes(32), provider); - await wethToken.depositTo(anotherWallet.address, { value: eth18 }).then((tx) => tx.wait()); - expect(await wethToken.balanceOf(anotherWallet.address)).to.equal(eth18); - }); - - it('Should withdraw WETH to another account', async function () { - const anotherWallet = new Wallet(ethers.utils.randomBytes(32), provider); - await wethToken.withdrawTo(anotherWallet.address, eth18.div(2)).then((tx) => tx.wait()); - expect(await anotherWallet.getBalance()).to.equal(eth18.div(2)); - expect(await wethToken.balanceOf(wallet.address)).to.equal(0); - }); - - it('Should fail withdrawing with insufficient balance', async function () { - await expect(wethToken.withdraw(1, { gasLimit: 100_000 })).to.be.reverted; - }); - - it('Should fail depositing directly to WETH bridge', async function () { - await expect( - wallet.sendTransaction({ - to: wethBridge.address, - value: eth18, - gasLimit: 100_000 - }) - ).to.be.reverted; - }); - - it('Should fail calling bridgeMint()', async function () { - await expect(wethToken.bridgeMint(wallet.address, eth18, { gasLimit: 100_000 })).to.be.revertedWith( - /Use deposit\/depositTo methods instead/ - ); - }); - - it('Should fail calling bridgeBurn() directly', async function () { - await expect(wethToken.bridgeBurn(wallet.address, eth18, { gasLimit: 100_000 })).to.be.reverted; - }); +const eth18 = ethers.utils.parseEther("18"); + +describe("WETH token & WETH bridge", function () { + const provider = new Provider(hre.config.networks.localhost.url); + const wallet = new Wallet(richAccount.privateKey, provider); + let wethToken: L2Weth; + let wethBridge: L2WethBridge; + + before("Deploy token and bridge", async function () { + const deployer = new Deployer(hre, wallet); + const wethTokenImpl = await deployer.deploy(await deployer.loadArtifact("L2Weth")); + const wethBridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2WethBridge")); + const randomAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + + const wethTokenProxy = await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [ + wethTokenImpl.address, + randomAddress, + "0x", + ]); + const wethBridgeProxy = (await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [ + wethBridgeImpl.address, + randomAddress, + "0x", + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ])) as any; + + wethToken = L2WethFactory.connect(wethTokenProxy.address, wallet); + wethBridge = L2WethBridgeFactory.connect(wethBridgeProxy.address, wallet); + + await wethToken.initialize("Wrapped Ether", "WETH"); + await wethToken.initializeV2(wethBridge.address, randomAddress); + + await wethBridge.initialize(randomAddress, randomAddress, wethToken.address); + }); + + it("Should deposit WETH by calling deposit()", async function () { + await wethToken.deposit({ value: eth18 }).then((tx) => tx.wait()); + expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18); + }); + + it("Should deposit WETH by sending", async function () { + await wallet + .sendTransaction({ + to: wethToken.address, + value: eth18, + }) + .then((tx) => tx.wait()); + expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.mul(2)); + }); + + it("Should fail depositing with random calldata", async function () { + await expect( + wallet.sendTransaction({ + data: ethers.utils.randomBytes(36), + to: wethToken.address, + value: eth18, + gasLimit: 100_000, + }) + ).to.be.reverted; + }); + + it("Should withdraw WETH to L2 ETH", async function () { + await wethToken.withdraw(eth18).then((tx) => tx.wait()); + expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18); + }); + + it("Should withdraw WETH to L1 ETH", async function () { + await expect(wethBridge.withdraw(wallet.address, wethToken.address, eth18.div(2))) + .to.emit(wethBridge, "WithdrawalInitiated") + .and.to.emit(wethToken, "BridgeBurn"); + expect(await wethToken.balanceOf(wallet.address)).to.equal(eth18.div(2)); + }); + + it("Should deposit WETH to another account", async function () { + const anotherWallet = new Wallet(ethers.utils.randomBytes(32), provider); + await wethToken.depositTo(anotherWallet.address, { value: eth18 }).then((tx) => tx.wait()); + expect(await wethToken.balanceOf(anotherWallet.address)).to.equal(eth18); + }); + + it("Should withdraw WETH to another account", async function () { + const anotherWallet = new Wallet(ethers.utils.randomBytes(32), provider); + await wethToken.withdrawTo(anotherWallet.address, eth18.div(2)).then((tx) => tx.wait()); + expect(await anotherWallet.getBalance()).to.equal(eth18.div(2)); + expect(await wethToken.balanceOf(wallet.address)).to.equal(0); + }); + + it("Should fail withdrawing with insufficient balance", async function () { + await expect(wethToken.withdraw(1, { gasLimit: 100_000 })).to.be.reverted; + }); + + it("Should fail depositing directly to WETH bridge", async function () { + await expect( + wallet.sendTransaction({ + to: wethBridge.address, + value: eth18, + gasLimit: 100_000, + }) + ).to.be.reverted; + }); + + it("Should fail calling bridgeMint()", async function () { + await expect(wethToken.bridgeMint(wallet.address, eth18, { gasLimit: 100_000 })).to.be.revertedWith( + /Use deposit\/depositTo methods instead/ + ); + }); + + it("Should fail calling bridgeBurn() directly", async function () { + await expect(wethToken.bridgeBurn(wallet.address, eth18, { gasLimit: 100_000 })).to.be.reverted; + }); });