diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2f261b0e..099f4a7b 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -9,14 +9,14 @@ jobs: build: name: End to End Test runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 30 steps: - uses: actions/checkout@v3 - - name: Set Node.js 18.x + - name: Set Node.js 18.18.x uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 18.18.x - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 00000000..5c5a28b0 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,7 @@ +{ + "extensions": ["ts"], + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 98ec7265..f3648521 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "test": "forge test", "lint": "forge fmt", "local:start": "cd scripts/localdev; ./start.sh", - "local:setup": "cd scripts/localdev; ./deploy.sh", - "local:test": "cd scripts/localdev; npx mocha --require mocha-suppress-logs ../e2e/", - "local:ci": "cd scripts/localdev; ./ci.sh && ./deploy.sh && npx mocha --require mocha-suppress-logs ../e2e/ && ./stop.sh", + "local:setup": "cd scripts/localdev; rm -rf .child.bridge.contracts.json .root.bridge.contracts.json; ./deploy.sh", + "local:test": "cd scripts/localdev; AXELAR_API_URL=skip npx mocha ../e2e/e2e.ts", + "local:ci": "cd scripts/localdev; rm -rf .child.bridge.contracts.json .root.bridge.contracts.json; ./ci.sh && ./deploy.sh && AXELAR_API_URL=skip npx mocha --require mocha-suppress-logs ../e2e/e2e.ts && ./stop.sh", "local:chainonly": "cd scripts/localdev; LOCAL_CHAIN_ONLY=true ./start.sh", - "local:axelaronly": "cd scripts/localdev; node axelar_setup.js", + "local:axelaronly": "cd scripts/localdev; npx ts-node axelar_setup.ts", "stop": "cd scripts/localdev; ./stop.sh" }, "author": "", @@ -27,7 +27,10 @@ "@axelar-network/axelar-local-dev": "^2.1.1-alpha.2", "@axelar-network/axelarjs-sdk": "^0.12.8", "@ethersproject/hardware-wallets": "^5.7.0", + "@ledgerhq/hw-app-eth": "^6.35.0", + "@ledgerhq/hw-transport-node-hid": "^6.28.0", "@openzeppelin/contracts": "^4.5.0", + "@types/chai-as-promised": "^7.1.8", "axios": "^0.27.2", "bip39": "^3.0.4", "config": "^3.3.9", diff --git a/scripts/bootstrap/.env.example b/scripts/bootstrap/.env.example index ad143850..a8ed55a2 100644 --- a/scripts/bootstrap/.env.example +++ b/scripts/bootstrap/.env.example @@ -1,74 +1,56 @@ -# Set prior to 1_deployer_funding.js +# Set prior to 0_pre_validation.js +# Name of the child chain MUST match Axelar's definition. CHILD_CHAIN_NAME= +# The RPC URL of child chain. CHILD_RPC_URL= +# The chain ID of the child chain. CHILD_CHAIN_ID= +# Name of the root chain MUST match Axelar's definition. ROOT_CHAIN_NAME= +# The RPC URL of root chain. ROOT_RPC_URL= +# The chain ID of the root chain. ROOT_CHAIN_ID= -## The admin EOA address on the child chain. -CHILD_ADMIN_ADDR= -## The private key for the admin EOA or "ledger" if using hardware wallet. -CHILD_ADMIN_EOA_SECRET= -## The deployer address on child chain. -CHILD_DEPLOYER_ADDR= -## The private key for the deployer on child chain or "ledger" if using hardware wallet. -CHILD_DEPLOYER_SECRET= -## The amount of fund deployer required on L2, unit is in IMX or 10^18 Wei. -CHILD_DEPLOYER_FUND= -## The deployer address on root chain. -ROOT_DEPLOYER_ADDR= -## The private key for the deployer on root chain or "ledger" if using hardware wallet. -ROOT_DEPLOYER_SECRET= -## The private key for rate admin or "ledger" if using hardware wallet. -ROOT_BRIDGE_RATE_ADMIN_SECRET= +## The deployer address on child & root chains. +DEPLOYER_ADDR= +## The private key for the deployer on child & root chains or "ledger" if using hardware wallet. +DEPLOYER_SECRET= +## The ledger index for the deployer on child & root chains, required if using ledger. +DEPLOYER_LEDGER_INDEX= +## The nonce reserved deployer address on child & root chains. +NONCE_RESERVED_DEPLOYER_ADDR= +## The nonce reserved deployer, or "ledger" if using hardware wallet. +NONCE_RESERVED_DEPLOYER_SECRET= +## The ledger index for the nonce reserved deployer. +NONCE_RESERVED_DEPLOYER_INDEX= +## The reserved nonce for token template deployment. +NONCE_RESERVED= ## The IMX token address on root chain. ROOT_IMX_ADDR= ## The Wrapped ETH token address on the root chain. ROOT_WETH_ADDR= -## The Axelar address for receive initial funding on the child chain. +## The Axelar address to receive initial funding on the child chain. AXELAR_EOA= +## The passport nonce reserver +PASSPORT_NONCE_RESERVER_ADDR= ## The amount of fund Axelar requested, unit is in IMX or 10^18 Wei. AXELAR_FUND= +## The amount of fund deployer to be left with after bootstrapping on L2, unit is in IMX or 10^18 Wei. +CHILD_DEPLOYER_FUND= +## The amount of fund nonce reserved deployer required on L2, unit is in IMX or 10^18 Wei. +CHILD_NONCE_RESERVED_DEPLOYER_FUND= +## The amount of fund passport reserver required on L2, unit is in IMX or 10^18 Wei. +PASSPORT_NONCE_RESERVER_FUND= ## The maximum amount of IMX that can be deposited to L2, unit is in IMX or 10^18 Wei. IMX_DEPOSIT_LIMIT= -## The address to perform child bridge upgrade. -CHILD_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child bridge. -CHILD_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in child bridge. -CHILD_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in child bridge. -CHILD_BRIDGE_UNPAUSER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in child bridge. -CHILD_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child adaptor. -CHILD_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_TARGET_MANAGER= -## The address to perform root adaptor upgrade. -ROOT_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root bridge. -ROOT_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in root bridge. -ROOT_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in root bridge. -ROOT_BRIDGE_UNPAUSER= -## The address to be assigned with VARIABLE_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_VARIABLE_MANAGER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root adaptor. -ROOT_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_TARGET_MANAGER= +## The privileged transaction Multisig address on the root chain. +ROOT_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the root chain. +ROOT_BREAKGLASS_ADDR= +## The privileged transaction Multisig address on the child chain. +CHILD_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the child chain. +CHILD_BREAKGLASS_ADDR= ## The capacity of the rate limit policy of IMX token, unit is in 10^18. RATE_LIMIT_IMX_CAPACITY= ## The refill rate of the rate limit policy of IMX token, unit is in 10^18. @@ -120,6 +102,10 @@ CHILD_GAS_SERVICE_ADDRESS= MULTISIG_CONTRACT_ADDRESS= ROOT_GATEWAY_ADDRESS= ROOT_GAS_SERVICE_ADDRESS= +## (Optional) to verify child contract after deployment +CHILD_CHAIN_BLOCKSCOUT_API_URL= +## (Optional) to verify root contract after deployment +ROOT_CHAIN_ETHERSCAN_API_KEY= # Set prior to bridge testing TEST_ACCOUNT_SECRET= \ No newline at end of file diff --git a/scripts/bootstrap/0_pre_validation.ts b/scripts/bootstrap/0_pre_validation.ts new file mode 100644 index 00000000..d909b1ec --- /dev/null +++ b/scripts/bootstrap/0_pre_validation.ts @@ -0,0 +1,159 @@ +// Pre validation +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, hasDuplicates } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +// The total supply of IMX +const TOTAL_SUPPLY = "2000000000"; + +// The contract ABI of IMX on L1. +const IMX_ABI = `[{"inputs":[{"internalType":"address","name":"minter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]`; + +function tryThrow(errorMsg: string) { + if (process.env["THROW_ON_FAIL"] != null) { + throw(errorMsg); + } else { + console.log(errorMsg); + } +} + +async function run() { + console.log("=======Start Pre Validation======="); + + // Check environment variables + requireEnv("CHILD_CHAIN_NAME"); + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + requireEnv("ROOT_CHAIN_NAME"); + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let deployerAddr = requireEnv("DEPLOYER_ADDR"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let reservedDeployerAddr = requireEnv("NONCE_RESERVED_DEPLOYER_ADDR"); + let reservedDeployerSecret = requireEnv("NONCE_RESERVED_DEPLOYER_SECRET"); + Number(requireEnv("NONCE_RESERVED")); + let rootIMXAddr = requireEnv("ROOT_IMX_ADDR"); + let rootWETHAddr = requireEnv("ROOT_WETH_ADDR"); + let axelarEOA = requireEnv("AXELAR_EOA"); + let passportDeployer = requireEnv("PASSPORT_NONCE_RESERVER_ADDR"); + let axelarFund = requireEnv("AXELAR_FUND"); + let childDeployerFund = requireEnv("CHILD_DEPLOYER_FUND"); + let childReservedDeployerFund = requireEnv("CHILD_NONCE_RESERVED_DEPLOYER_FUND"); + let passportDeployerFund = requireEnv("PASSPORT_NONCE_RESERVER_FUND"); + let imxDepositLimit = requireEnv("IMX_DEPOSIT_LIMIT"); + requireEnv("RATE_LIMIT_IMX_CAPACITY"); + requireEnv("RATE_LIMIT_IMX_REFILL_RATE"); + requireEnv("RATE_LIMIT_IMX_LARGE_THRESHOLD"); + requireEnv("RATE_LIMIT_ETH_CAPACITY"); + requireEnv("RATE_LIMIT_ETH_REFILL_RATE"); + requireEnv("RATE_LIMIT_ETH_LARGE_THRESHOLD"); + requireEnv("RATE_LIMIT_USDC_ADDR"); + requireEnv("RATE_LIMIT_USDC_CAPACITY"); + requireEnv("RATE_LIMIT_USDC_REFILL_RATE"); + requireEnv("RATE_LIMIT_USDC_LARGE_THRESHOLD"); + // requireEnv("RATE_LIMIT_GU_ADDR"); + // requireEnv("RATE_LIMIT_GU_CAPACITY"); + // requireEnv("RATE_LIMIT_GU_REFILL_RATE"); + // requireEnv("RATE_LIMIT_GU_LARGE_THRESHOLD"); + // requireEnv("RATE_LIMIT_CHECKMATE_ADDR"); + // requireEnv("RATE_LIMIT_CHECKMATE_CAPACITY"); + // requireEnv("RATE_LIMIT_CHECKMATE_REFILL_RATE"); + // requireEnv("RATE_LIMIT_CHECKMATE_LARGE_THRESHOLD"); + // requireEnv("RATE_LIMIT_GOG_ADDR"); + // requireEnv("RATE_LIMIT_GOG_CAPACITY"); + // requireEnv("RATE_LIMIT_GOG_REFILL_RATE"); + // requireEnv("RATE_LIMIT_GOG_LARGE_THRESHOLD"); + + const childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + const rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); + let deployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + deployerWallet = new LedgerSigner(childProvider, derivationPath); + } else { + deployerWallet = new ethers.Wallet(deployerSecret, childProvider); + } + let actualDeployerAddress = await deployerWallet.getAddress(); + if (deployerWallet instanceof LedgerSigner) { + deployerWallet.close(); + } + + let reservedWallet; + if (reservedDeployerSecret == "ledger") { + let index = requireEnv("NONCE_RESERVED_DEPLOYER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + reservedWallet = new LedgerSigner(childProvider, derivationPath); + } else { + reservedWallet = new ethers.Wallet(reservedDeployerSecret, childProvider); + } + let actualReservedDeployerAddress = await reservedWallet.getAddress(); + + // Check deployer address matches deployer addr + if (actualDeployerAddress != deployerAddr) { + tryThrow("Deployer addresses mismatch, expect " + deployerAddr + " actual " + actualDeployerAddress); + } + if (actualReservedDeployerAddress != reservedDeployerAddr) { + tryThrow("Reserved Nonce deployer addresses mismatch, expect " + reservedDeployerAddr + " actual " + actualReservedDeployerAddress); + } + + // Check duplicates + if (hasDuplicates([actualDeployerAddress, actualReservedDeployerAddress, axelarEOA, passportDeployer])) { + throw("Duplicate address detected!"); + } + if (hasDuplicates([rootIMXAddr, rootWETHAddr])) { + throw("Duplicate address detected!"); + } + + // Check deployer fund on root chain and child chain. + let IMX = new ethers.Contract(rootIMXAddr, IMX_ABI, rootProvider); + let rootDeployerETHBalance = await rootProvider.getBalance(actualDeployerAddress); + let rootReservedDeployerETHBalance = await rootProvider.getBalance(actualReservedDeployerAddress); + if (rootDeployerETHBalance.lt(ethers.utils.parseEther("0.1"))) { + tryThrow("Deployer on root chain needs to have at least 0.1 ETH, got " + ethers.utils.formatEther(rootDeployerETHBalance)); + } + if (rootReservedDeployerETHBalance.lt(ethers.utils.parseEther("0.1"))) { + tryThrow("Reserved deployer on root chain needs to have at least 0.1 ETH, got " + ethers.utils.formatEther(rootReservedDeployerETHBalance)); + } + let rootDeployerIMXBalance = await IMX.balanceOf(actualDeployerAddress); + let axelarRequiredIMX = ethers.utils.parseEther(axelarFund); + let deployerRequiredIMX = ethers.utils.parseEther(childDeployerFund); + let reservedDeployerRequiredIMX = ethers.utils.parseEther(childReservedDeployerFund); + let passportRequiredIMX = ethers.utils.parseEther(passportDeployerFund); + if (axelarRequiredIMX.lt(ethers.utils.parseEther("500.0"))) { + tryThrow("Axelar on child chain should request at least 500 IMX, got" + ethers.utils.formatEther(axelarRequiredIMX)); + } + if (deployerRequiredIMX.lt(ethers.utils.parseEther("250.0"))) { + tryThrow("Deployer on child chain should request at least 500 IMX, got" + ethers.utils.formatEther(deployerRequiredIMX)); + } + if (reservedDeployerRequiredIMX.lt(ethers.utils.parseEther("100.0"))) { + tryThrow("Reserved deployer on child chain should request at least 100 IMX, got" + ethers.utils.formatEther(reservedDeployerRequiredIMX)); + } + if (passportRequiredIMX.lt(ethers.utils.parseEther("100.0"))) { + tryThrow("Passport deployer on child chain should request at least 100 IMX, got" + ethers.utils.formatEther(passportRequiredIMX)); + } + let extraIMX = ethers.utils.parseEther("100.0"); + let requiredIMX = axelarRequiredIMX.add(deployerRequiredIMX).add(reservedDeployerRequiredIMX).add(extraIMX); + if (rootDeployerIMXBalance.lt(requiredIMX)) { + tryThrow("Deployer on root chain needs to have at least " + ethers.utils.formatEther(requiredIMX) + " IMX, got " + ethers.utils.formatEther(rootDeployerIMXBalance)); + } + let childDeployerIMXBalance = await childProvider.getBalance(actualDeployerAddress); + if (!childDeployerIMXBalance.eq(ethers.utils.parseEther(TOTAL_SUPPLY))) { + tryThrow("Deployer on child chain needs to have 2B units of pre-mined IMX, got " + ethers.utils.formatEther(childDeployerIMXBalance)); + } + + // Check IMX deposit limit + let depositLimit = ethers.utils.parseEther(imxDepositLimit); + if (depositLimit.gt(ethers.utils.parseEther("200000000"))) { + tryThrow("Deposit limit should be at most 200m, got " + ethers.utils.formatEther(depositLimit)); + } + if (depositLimit.lt(ethers.utils.parseEther("2000000"))) { + tryThrow("Deposit limit should be at least 2m, got " + ethers.utils.formatEther(depositLimit)); + } + + console.log("=======End Pre Validation======="); +} +run(); \ No newline at end of file diff --git a/scripts/bootstrap/1_deployer_funding.js b/scripts/bootstrap/1_deployer_funding.js deleted file mode 100644 index 7266624f..00000000 --- a/scripts/bootstrap/1_deployer_funding.js +++ /dev/null @@ -1,70 +0,0 @@ -// Deployer funding -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const { LedgerSigner } = require('@ethersproject/hardware-wallets') - -async function run() { - console.log("=======Start Deployer Funding======="); - - // Check environment variables - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let adminEOASecret = helper.requireEnv("CHILD_ADMIN_EOA_SECRET"); - let axelarEOA = helper.requireEnv("AXELAR_EOA"); - let axelarFund = helper.requireEnv("AXELAR_FUND"); - let deployerEOA = helper.requireEnv("CHILD_DEPLOYER_ADDR"); - let deployerFund = helper.requireEnv("CHILD_DEPLOYER_FUND"); - - // Get admin EOA address - const childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - let adminWallet; - if (adminEOASecret == "ledger") { - adminWallet = new LedgerSigner(childProvider); - } else { - adminWallet = new ethers.Wallet(adminEOASecret, childProvider); - } - let adminAddr = await adminWallet.getAddress(); - console.log("Admin address is: ", adminAddr); - - // Check duplicates - if (helper.hasDuplicates([adminAddr, axelarEOA, deployerEOA])) { - throw("Duplicate address detected!"); - } - - // Execute - console.log("Axelar EOA now has: ", ethers.utils.formatEther(await childProvider.getBalance(axelarEOA))); - console.log("Deployer EOA now has: ", ethers.utils.formatEther(await childProvider.getBalance(deployerEOA))); - console.log("Fund Axelar and deployer on child chain in..."); - await helper.waitForConfirmation(); - - let [priorityFee, maxFee] = await helper.getFee(adminWallet); - console.log("Transfer value to axelar..."); - let resp = await adminWallet.sendTransaction({ - to: axelarEOA, - value: ethers.utils.parseEther(axelarFund), - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }) - console.log("Transaction submitted: " + JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, childProvider); - - [priorityFee, maxFee] = await helper.getFee(adminWallet); - console.log("Transfer value to deployer..."); - resp = await adminWallet.sendTransaction({ - to: deployerEOA, - value: ethers.utils.parseEther(deployerFund), - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }) - console.log("Transaction submitted: " + JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, childProvider); - - // Print target balance - console.log("Axelar EOA now has: ", ethers.utils.formatEther(await childProvider.getBalance(axelarEOA))); - console.log("Deployer EOA now has: ", ethers.utils.formatEther(await childProvider.getBalance(deployerEOA))); - - console.log("=======End Deployer Funding======="); -} -run(); \ No newline at end of file diff --git a/scripts/bootstrap/1_deployer_funding.ts b/scripts/bootstrap/1_deployer_funding.ts new file mode 100644 index 00000000..2d1622a5 --- /dev/null +++ b/scripts/bootstrap/1_deployer_funding.ts @@ -0,0 +1,100 @@ +// Deployer funding +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, waitForConfirmation, waitForReceipt, getFee, hasDuplicates } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +async function run() { + console.log("=======Start Deployer Funding======="); + + // Check environment variables + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let reservedDeployerAddr = requireEnv("NONCE_RESERVED_DEPLOYER_ADDR"); + let reservedDeployerFund = requireEnv("CHILD_NONCE_RESERVED_DEPLOYER_FUND"); + let axelarEOA = requireEnv("AXELAR_EOA"); + let passportDeployer = requireEnv("PASSPORT_NONCE_RESERVER_ADDR"); + let axelarFund = requireEnv("AXELAR_FUND"); + let passportDeployerFund = requireEnv("PASSPORT_NONCE_RESERVER_FUND"); + + // Get deployer address + const childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + let childDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + childDeployerWallet = new LedgerSigner(childProvider, derivationPath); + } else { + childDeployerWallet = new ethers.Wallet(deployerSecret, childProvider); + } + let deployerAddr = await childDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); + + // Check duplicates + if (hasDuplicates([deployerAddr, axelarEOA, reservedDeployerAddr, passportDeployer])) { + throw("Duplicate address detected!"); + } + + // Execute + console.log("Nonce reserved deployer now has: ", ethers.utils.formatEther(await childProvider.getBalance(reservedDeployerAddr))); + console.log("Axelar EOA now has: ", ethers.utils.formatEther(await childProvider.getBalance(axelarEOA))); + console.log("Passport deployer now has: ", ethers.utils.formatEther(await childProvider.getBalance(passportDeployer))); + console.log("Fund Axelar, deployers on child chain in..."); + await waitForConfirmation(); + + if ((await childProvider.getBalance(reservedDeployerAddr)).gte(ethers.utils.parseEther(reservedDeployerFund))) { + console.log("Nonce reserved deployer has already got requested amount, skip."); + } else { + let [priorityFee, maxFee] = await getFee(childProvider); + console.log("Transfer value to reserved nonce deployer..."); + let resp = await childDeployerWallet.sendTransaction({ + to: reservedDeployerAddr, + value: ethers.utils.parseEther(reservedDeployerFund), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }) + console.log("Transaction submitted: " + JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, childProvider); + } + + if ((await childProvider.getBalance(axelarEOA)).gte(ethers.utils.parseEther(axelarFund))) { + console.log("Axelar has already got requested amount, skip."); + } else { + let [priorityFee, maxFee] = await getFee(childProvider); + console.log("Transfer value to axelar..."); + let resp = await childDeployerWallet.sendTransaction({ + to: axelarEOA, + value: ethers.utils.parseEther(axelarFund), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }) + console.log("Transaction submitted: " + JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, childProvider); + } + + if ((await childProvider.getBalance(passportDeployer)).gte(ethers.utils.parseEther(passportDeployerFund))) { + console.log("Passport deployer has already got requested amount, skip."); + } else { + let [priorityFee, maxFee] = await getFee(childProvider); + console.log("Transfer value to Passport deployer..."); + let resp = await childDeployerWallet.sendTransaction({ + to: passportDeployer, + value: ethers.utils.parseEther(passportDeployerFund), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }) + console.log("Transaction submitted: " + JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, childProvider); + } + + // Print target balance + console.log("Nonce reserved deployer now has: ", ethers.utils.formatEther(await childProvider.getBalance(reservedDeployerAddr))); + console.log("Axelar EOA now has: ", ethers.utils.formatEther(await childProvider.getBalance(axelarEOA))); + console.log("Passport deployer now has: ", ethers.utils.formatEther(await childProvider.getBalance(passportDeployer))); + + console.log("=======End Deployer Funding======="); +} +run(); \ No newline at end of file diff --git a/scripts/bootstrap/2_deployment_validation.js b/scripts/bootstrap/2_deployment_validation.js deleted file mode 100644 index ea763836..00000000 --- a/scripts/bootstrap/2_deployment_validation.js +++ /dev/null @@ -1,60 +0,0 @@ -// Deployment validation -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); - -async function run() { - console.log("=======Start Deployment Validation======="); - - // Check environment variables - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let rootRPCURL = helper.requireEnv("ROOT_RPC_URL"); - let rootChainID = helper.requireEnv("ROOT_CHAIN_ID"); - let childGatewayAddr = helper.requireEnv("CHILD_GATEWAY_ADDRESS"); - let childGasServiceAddr = helper.requireEnv("CHILD_GAS_SERVICE_ADDRESS"); - let multisigAddr = helper.requireEnv("MULTISIG_CONTRACT_ADDRESS"); - let rootGatewayAddr = helper.requireEnv("ROOT_GATEWAY_ADDRESS"); - let rootGasService = helper.requireEnv("ROOT_GAS_SERVICE_ADDRESS"); - - // Check duplicates - if (helper.hasDuplicates([childGatewayAddr, childGasServiceAddr, multisigAddr])) { - throw("Duplicate address detected!"); - } - if (helper.hasDuplicates([rootGatewayAddr, rootGasService])) { - throw("Duplicate address detected!"); - } - - const childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - const rootProvider = new ethers.providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); - - // Check child chain. - console.log("Check contracts on child chain..."); - console.log("Check gateway contract...") - await helper.requireNonEmptyCode(childProvider, childGatewayAddr); - console.log("Succeed."); - console.log("Check gas service contract...") - await helper.requireNonEmptyCode(childProvider, childGasServiceAddr); - console.log("Succeed."); - if (process.env["SKIP_MULTISIG_CHECK"] != null) { - console.log("Skip multisig contract check..."); - } else { - console.log("Check multisig contract..."); - await helper.requireNonEmptyCode(childProvider, multisigAddr); - console.log("Succeed."); - } - - // Check root chain. - console.log("Check contracts on root chain..."); - console.log("Check gateway contract..."); - await helper.requireNonEmptyCode(rootProvider, rootGatewayAddr); - console.log("Succeed."); - console.log("Check gas service contract..."); - await helper.requireNonEmptyCode(rootProvider, rootGasService); - console.log("Succeed."); - - console.log("=======End Deployment Validation======="); -} - -run(); \ No newline at end of file diff --git a/scripts/bootstrap/2_deployment_validation.ts b/scripts/bootstrap/2_deployment_validation.ts new file mode 100644 index 00000000..a006067c --- /dev/null +++ b/scripts/bootstrap/2_deployment_validation.ts @@ -0,0 +1,61 @@ +// Deployment validation +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, hasDuplicates, requireNonEmptyCode } from "../helpers/helpers"; +import { RetryProvider } from "../helpers/retry"; + +async function run() { + console.log("=======Start Deployment Validation======="); + + // Check environment variables + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let childGatewayAddr = requireEnv("CHILD_GATEWAY_ADDRESS"); + let childGasServiceAddr = requireEnv("CHILD_GAS_SERVICE_ADDRESS"); + let multisigAddr = requireEnv("MULTISIG_CONTRACT_ADDRESS"); + let rootGatewayAddr = requireEnv("ROOT_GATEWAY_ADDRESS"); + let rootGasService = requireEnv("ROOT_GAS_SERVICE_ADDRESS"); + + // Check duplicates + if (hasDuplicates([childGatewayAddr, childGasServiceAddr, multisigAddr])) { + throw("Duplicate address detected!"); + } + if (hasDuplicates([rootGatewayAddr, rootGasService])) { + throw("Duplicate address detected!"); + } + + const childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + const rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); + + // Check child chain. + console.log("Check contracts on child chain..."); + console.log("Check gateway contract...") + await requireNonEmptyCode(childProvider, childGatewayAddr); + console.log("Succeed."); + console.log("Check gas service contract...") + await requireNonEmptyCode(childProvider, childGasServiceAddr); + console.log("Succeed."); + if (process.env["SKIP_MULTISIG_CHECK"] != null) { + console.log("Skip multisig contract check..."); + } else { + console.log("Check multisig contract..."); + await requireNonEmptyCode(childProvider, multisigAddr); + console.log("Succeed."); + } + + // Check root chain. + console.log("Check contracts on root chain..."); + console.log("Check gateway contract..."); + await requireNonEmptyCode(rootProvider, rootGatewayAddr); + console.log("Succeed."); + console.log("Check gas service contract..."); + await requireNonEmptyCode(rootProvider, rootGasService); + console.log("Succeed."); + + console.log("=======End Deployment Validation======="); +} + +run(); \ No newline at end of file diff --git a/scripts/bootstrap/3_child_deployment.js b/scripts/bootstrap/3_child_deployment.ts similarity index 55% rename from scripts/bootstrap/3_child_deployment.js rename to scripts/bootstrap/3_child_deployment.ts index 509ff107..37af73e2 100644 --- a/scripts/bootstrap/3_child_deployment.js +++ b/scripts/bootstrap/3_child_deployment.ts @@ -1,12 +1,10 @@ // Deploy child contracts -'use strict'; -require('dotenv').config(); -const deploy = require("../deploy/child_deployment.js"); +import { deployChildContracts } from "../deploy/child_deployment"; async function run() { console.log("=======Start Child Deployment======="); - await deploy.deployChildContracts(); + await deployChildContracts(); console.log("=======End Child Deployment======="); } diff --git a/scripts/bootstrap/4_root_deployment.js b/scripts/bootstrap/4_root_deployment.ts similarity index 55% rename from scripts/bootstrap/4_root_deployment.js rename to scripts/bootstrap/4_root_deployment.ts index 48ea2d4f..546aaf11 100644 --- a/scripts/bootstrap/4_root_deployment.js +++ b/scripts/bootstrap/4_root_deployment.ts @@ -1,12 +1,10 @@ // Deploy root contracts -'use strict'; -require('dotenv').config(); -const deploy = require("../deploy/root_deployment.js"); +import { deployRootContracts } from "../deploy/root_deployment"; async function run() { console.log("=======Start Root Deployment======="); - await deploy.deployRootContracts(); + await deployRootContracts(); console.log("=======End Root Deployment======="); } diff --git a/scripts/bootstrap/5_child_initialisation.js b/scripts/bootstrap/5_child_initialisation.ts similarity index 56% rename from scripts/bootstrap/5_child_initialisation.js rename to scripts/bootstrap/5_child_initialisation.ts index 09fc59e6..47fa9c60 100644 --- a/scripts/bootstrap/5_child_initialisation.js +++ b/scripts/bootstrap/5_child_initialisation.ts @@ -1,12 +1,10 @@ // Initialise child contracts -'use strict'; -require('dotenv').config(); -const init = require("../deploy/child_initialisation.js"); +import { initialiseChildContracts } from "../deploy/child_initialisation"; async function run() { console.log("=======Start Child Initialisation======="); - await init.initialiseChildContracts(); + await initialiseChildContracts(); console.log("=======End Child Initialisation======="); } diff --git a/scripts/bootstrap/6_imx_burning.js b/scripts/bootstrap/6_imx_burning.js deleted file mode 100644 index 57e0bfb0..00000000 --- a/scripts/bootstrap/6_imx_burning.js +++ /dev/null @@ -1,97 +0,0 @@ -// IMX burning -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const { LedgerSigner } = require('@ethersproject/hardware-wallets') -const fs = require('fs'); - -async function run() { - console.log("=======Start IMX Burning======="); - - // Check environment variables - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let adminEOASecret = helper.requireEnv("CHILD_ADMIN_EOA_SECRET"); - let multisigAddr = helper.requireEnv("MULTISIG_CONTRACT_ADDRESS"); - let imxDepositLimit = helper.requireEnv("IMX_DEPOSIT_LIMIT"); - - // Read from contract file. - let data = fs.readFileSync(".child.bridge.contracts.json", 'utf-8'); - let childContracts = JSON.parse(data); - let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; - - // Get admin address - const childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - let adminWallet; - if (adminEOASecret == "ledger") { - adminWallet = new LedgerSigner(childProvider); - } else { - adminWallet = new ethers.Wallet(adminEOASecret, childProvider); - } - let adminAddr = await adminWallet.getAddress(); - console.log("Admin address is: ", adminAddr); - - // Check duplicates - if (helper.hasDuplicates([adminAddr, childBridgeAddr, multisigAddr])) { - throw("Duplicate address detected!"); - } - - // Execute - let adminBal = await childProvider.getBalance(adminAddr); - let bridgeBal = await childProvider.getBalance(childBridgeAddr); - let multisigBal = await childProvider.getBalance(multisigAddr); - console.log("Admin balance: ", ethers.utils.formatEther(adminBal)); - console.log("Bridge balance: ", ethers.utils.formatEther(bridgeBal)); - console.log("Multisig balance: ", ethers.utils.formatEther(multisigBal)); - - if (adminBal.lt(ethers.utils.parseEther("0.01"))) { - console.log("IMX Burning has already been done, skip.") - return; - } - - console.log("Burn IMX in..."); - await helper.waitForConfirmation(); - - let childBridgeObj = JSON.parse(fs.readFileSync('../../out/ChildERC20Bridge.sol/ChildERC20Bridge.json', 'utf8')); - let childBridge = new ethers.Contract(childBridgeAddr, childBridgeObj.abi, childProvider); - - console.log("Transfer " + imxDepositLimit + " IMX to child bridge..."); - let [priorityFee, maxFee] = await helper.getFee(adminWallet); - let resp = await childBridge.connect(adminWallet).privilegedDeposit({ - value: ethers.utils.parseEther(imxDepositLimit), - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }) - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)) - await helper.waitForReceipt(resp.hash, childProvider); - - adminBal = await childProvider.getBalance(adminAddr); - bridgeBal = await childProvider.getBalance(childBridgeAddr); - multisigBal = await childProvider.getBalance(multisigAddr); - console.log("Admin balance: ", ethers.utils.formatEther(adminBal)); - console.log("Bridge balance: ", ethers.utils.formatEther(bridgeBal)); - console.log("Multisig balance: ", ethers.utils.formatEther(multisigBal)); - - // Transfer to multisig - console.log("Transfer remaining to multisig..."); - [priorityFee, maxFee] = await helper.getFee(adminWallet); - resp = await adminWallet.sendTransaction({ - to: multisigAddr, - value: adminBal.sub(ethers.utils.parseEther("0.01")), - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)) - await helper.waitForReceipt(resp.hash, childProvider); - - adminBal = await childProvider.getBalance(adminAddr); - bridgeBal = await childProvider.getBalance(childBridgeAddr); - multisigBal = await childProvider.getBalance(multisigAddr); - console.log("Admin balance: ", ethers.utils.formatEther(adminBal)); - console.log("Bridge balance: ", ethers.utils.formatEther(bridgeBal)); - console.log("Multisig balance: ", ethers.utils.formatEther(multisigBal)); - - console.log("=======End IMX Burning======="); -} -run(); \ No newline at end of file diff --git a/scripts/bootstrap/6_imx_burning.ts b/scripts/bootstrap/6_imx_burning.ts new file mode 100644 index 00000000..632d23da --- /dev/null +++ b/scripts/bootstrap/6_imx_burning.ts @@ -0,0 +1,96 @@ +// IMX burning +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, waitForConfirmation, hasDuplicates, waitForReceipt, getFee, getContract, getChildContracts } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +async function run() { + console.log("=======Start IMX Burning======="); + + // Check environment variables + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let multisigAddr = requireEnv("MULTISIG_CONTRACT_ADDRESS"); + let imxDepositLimit = requireEnv("IMX_DEPOSIT_LIMIT"); + let deployerFund = requireEnv("CHILD_DEPLOYER_FUND"); + + // Read from contract file. + let childContracts = getChildContracts(); + let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; + + // Get deployer address + const childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + let childDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + childDeployerWallet = new LedgerSigner(childProvider, derivationPath); + } else { + childDeployerWallet = new ethers.Wallet(deployerSecret, childProvider); + } + let deployerAddr = await childDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); + + // Check duplicates + if (hasDuplicates([deployerAddr, childBridgeAddr, multisigAddr])) { + throw("Duplicate address detected!"); + } + + // Execute + let deployerBal = await childProvider.getBalance(deployerAddr); + let bridgeBal = await childProvider.getBalance(childBridgeAddr); + let multisigBal = await childProvider.getBalance(multisigAddr); + console.log("Deployer balance: ", ethers.utils.formatEther(deployerBal)); + console.log("Bridge balance: ", ethers.utils.formatEther(bridgeBal)); + console.log("Multisig balance: ", ethers.utils.formatEther(multisigBal)); + + console.log("Burn IMX in..."); + await waitForConfirmation(); + + if ((await childProvider.getBalance(childBridgeAddr)).gte(ethers.utils.parseEther(imxDepositLimit))) { + console.log("Child bridge has already got burned IMX, skip."); + } else { + console.log("Transfer " + imxDepositLimit + " IMX to child bridge..."); + let [priorityFee, maxFee] = await getFee(childProvider); + let childBridge = getContract("ChildERC20Bridge", childBridgeAddr, childProvider); + let resp = await childBridge.connect(childDeployerWallet).privilegedDeposit({ + value: ethers.utils.parseEther(imxDepositLimit), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }) + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)) + await waitForReceipt(resp.hash, childProvider); + } + + // Transfer to multisig + let remain = ethers.utils.parseEther(deployerFund); + deployerBal = await childProvider.getBalance(deployerAddr); + if (deployerBal.lte(remain)) { + console.log("Multisig has already got remaining burned IMX, skip."); + } else { + console.log("Transfer remaining to multisig..."); + let toTransfer = deployerBal.sub(remain); + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childDeployerWallet.sendTransaction({ + to: multisigAddr, + value: toTransfer, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)) + await waitForReceipt(resp.hash, childProvider); + } + + deployerBal = await childProvider.getBalance(deployerAddr); + bridgeBal = await childProvider.getBalance(childBridgeAddr); + multisigBal = await childProvider.getBalance(multisigAddr); + console.log("Deployer balance: ", ethers.utils.formatEther(deployerBal)); + console.log("Bridge balance: ", ethers.utils.formatEther(bridgeBal)); + console.log("Multisig balance: ", ethers.utils.formatEther(multisigBal)); + + console.log("=======End IMX Burning======="); +} +run(); \ No newline at end of file diff --git a/scripts/bootstrap/7_imx_rebalancing.js b/scripts/bootstrap/7_imx_rebalancing.ts similarity index 76% rename from scripts/bootstrap/7_imx_rebalancing.js rename to scripts/bootstrap/7_imx_rebalancing.ts index e2bf463c..27c43ea6 100644 --- a/scripts/bootstrap/7_imx_rebalancing.js +++ b/scripts/bootstrap/7_imx_rebalancing.ts @@ -1,10 +1,10 @@ // IMX rebalancing -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const { LedgerSigner } = require('@ethersproject/hardware-wallets') -const fs = require('fs'); +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, waitForConfirmation, waitForReceipt, getFee, hasDuplicates, getChildContracts, getRootContracts } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; // The total supply of IMX const TOTAL_SUPPLY = "2000000000"; @@ -16,39 +16,39 @@ async function run() { console.log("=======Start IMX Rebalancing======="); // Check environment variables - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let rootRPCURL = helper.requireEnv("ROOT_RPC_URL"); - let rootChainID = helper.requireEnv("ROOT_CHAIN_ID"); - let rootDeployerSecret = helper.requireEnv("ROOT_DEPLOYER_SECRET"); - let multisigAddr = helper.requireEnv("MULTISIG_CONTRACT_ADDRESS"); - let rootIMXAddr = helper.requireEnv("ROOT_IMX_ADDR"); + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let multisigAddr = requireEnv("MULTISIG_CONTRACT_ADDRESS"); + let rootIMXAddr = requireEnv("ROOT_IMX_ADDR"); // Read from contract file. - let data = fs.readFileSync(".child.bridge.contracts.json", 'utf-8'); - let childContracts = JSON.parse(data); + let childContracts = getChildContracts(); let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; - data = fs.readFileSync(".root.bridge.contracts.json", 'utf-8'); - let rootContracts = JSON.parse(data); + let rootContracts = getRootContracts(); let rootBridgeAddr = rootContracts.ROOT_BRIDGE_ADDRESS; - // Get admin address - const childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - const rootProvider = new ethers.providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); - let adminWallet; - if (rootDeployerSecret == "ledger") { - adminWallet = new LedgerSigner(rootProvider); + // Get deployer address + const childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + const rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); + let rootDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + rootDeployerWallet = new LedgerSigner(rootProvider, derivationPath); } else { - adminWallet = new ethers.Wallet(rootDeployerSecret, rootProvider); + rootDeployerWallet = new ethers.Wallet(deployerSecret, rootProvider); } - let adminAddr = await adminWallet.getAddress(); - console.log("Deployer address is: ", adminAddr); + let deployerAddr = await rootDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); // Check duplicates - if (helper.hasDuplicates([adminAddr, childBridgeAddr, multisigAddr])) { + if (hasDuplicates([deployerAddr, childBridgeAddr, multisigAddr])) { throw("Duplicate address detected!"); } - if (helper.hasDuplicates([adminAddr, rootBridgeAddr, rootIMXAddr])) { + if (hasDuplicates([deployerAddr, rootBridgeAddr, rootIMXAddr])) { throw("Duplicate address detected!"); } @@ -60,29 +60,29 @@ async function run() { console.log("The amount to balance on L1 is: ", ethers.utils.formatEther(balanceAmt)); let IMX = new ethers.Contract(rootIMXAddr, IMX_ABI, rootProvider); - let adminL1Balance = await IMX.balanceOf(adminAddr); + let deployerL1Balance = await IMX.balanceOf(deployerAddr); let rootBridgeBalance = await IMX.balanceOf(rootBridgeAddr); - console.log("Admin L1 IMX balance: ", ethers.utils.formatEther(adminL1Balance)); + console.log("Deployer L1 IMX balance: ", ethers.utils.formatEther(deployerL1Balance)); console.log("Root bridge L1 IMX balance: ", ethers.utils.formatEther(rootBridgeBalance)); - - if (rootBridgeBalance.gt(ethers.utils.parseEther("1.0"))) { - console.log("IMX Rebalancing has already been done, skip.") - return; - } console.log("Rebalance in..."); - await helper.waitForConfirmation(); + await waitForConfirmation(); + + if (deployerL1Balance.lt(balanceAmt)) { + console.log("Insufficient balance to rebalance (already balanced?), skip."); + return; + } // Rebalancing - console.log("Transfer...") - let resp = await IMX.connect(adminWallet).transfer(rootBridgeAddr, balanceAmt); + console.log("Rebalancing...") + let resp = await IMX.connect(rootDeployerWallet).transfer(rootBridgeAddr, balanceAmt); console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)) - await helper.waitForReceipt(resp.hash, rootProvider); + await waitForReceipt(resp.hash, rootProvider); - adminL1Balance = await IMX.balanceOf(adminAddr); + deployerL1Balance = await IMX.balanceOf(deployerAddr); rootBridgeBalance = await IMX.balanceOf(rootBridgeAddr); - console.log("Admin L1 IMX balance: ", ethers.utils.formatEther(adminL1Balance)); + console.log("Deployer L1 IMX balance: ", ethers.utils.formatEther(deployerL1Balance)); console.log("Root bridge L1 IMX balance: ", ethers.utils.formatEther(rootBridgeBalance)); console.log("=======End IMX Rebalancing======="); diff --git a/scripts/bootstrap/8_root_initialisation.js b/scripts/bootstrap/8_root_initialisation.ts similarity index 56% rename from scripts/bootstrap/8_root_initialisation.js rename to scripts/bootstrap/8_root_initialisation.ts index a193b67c..3693345b 100644 --- a/scripts/bootstrap/8_root_initialisation.js +++ b/scripts/bootstrap/8_root_initialisation.ts @@ -1,12 +1,10 @@ // Initialise root contracts -'use strict'; -require('dotenv').config(); -const init = require("../deploy/root_initialisation.js"); +import { initialiseRootContracts } from "../deploy/root_initialisation"; async function run() { console.log("=======Start Root Initialisation======="); - await init.initialiseRootContracts(); + await initialiseRootContracts(); console.log("=======End Root Initialisation======="); } diff --git a/scripts/bootstrap/9_test_preparation.ts b/scripts/bootstrap/9_test_preparation.ts new file mode 100644 index 00000000..7fcbac83 --- /dev/null +++ b/scripts/bootstrap/9_test_preparation.ts @@ -0,0 +1,106 @@ +// Prepare for test +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers, utils } from "ethers"; +import { deployRootContract, getContract, getRootContracts, requireEnv, saveRootContracts, waitForConfirmation, waitForReceipt } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +async function run() { + console.log("=======Start Test Preparation======="); + + // Check environment variables + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let testAccountKey = requireEnv("TEST_ACCOUNT_SECRET"); + let rootPrivilegedMultisig = requireEnv("ROOT_PRIVILEGED_MULTISIG_ADDR"); + + // Get deployer address + const rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); + let rootDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + rootDeployerWallet = new LedgerSigner(rootProvider, derivationPath); + } else { + rootDeployerWallet = new ethers.Wallet(deployerSecret, rootProvider); + } + let deployerAddr = await rootDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); + + let rootTestWallet = new ethers.Wallet(testAccountKey, rootProvider); + + let rootContracts = getRootContracts(); + let rootBridge = getContract("RootERC20BridgeFlowRate", rootContracts.ROOT_BRIDGE_ADDRESS, rootProvider); + + // Execute + console.log("Prepare test in..."); + await waitForConfirmation(); + + // Deploy a custom token + let rootCustomToken; + if (rootContracts.ROOT_TEST_CUSTOM_TOKEN != "") { + console.log("Root test custom token has already been deployed to: " + rootContracts.ROOT_TEST_CUSTOM_TOKEN + ", skip."); + rootCustomToken = getContract("ERC20PresetMinterPauser", rootContracts.ROOT_TEST_CUSTOM_TOKEN, rootProvider); + } else { + console.log("Deploy root test custom token..."); + rootCustomToken = await deployRootContract("ERC20PresetMinterPauser", rootDeployerWallet, null, "Custom Token", "CTK"); + await waitForReceipt(rootCustomToken.deployTransaction.hash, rootProvider); + console.log("Custom token deployed to: ", rootCustomToken.address); + } + rootContracts.ROOT_TEST_CUSTOM_TOKEN=rootCustomToken.address; + saveRootContracts(rootContracts); + console.log("Deployed to ROOT_TEST_CUSTOM_TOKEN: ", rootCustomToken.address); + + // Mint tokens + if ((await rootCustomToken.balanceOf(rootTestWallet.address)).toString() != "0") { + console.log("Test account has already been given test tokens, skip."); + } else { + console.log("Mint tokens..."); + let resp = await rootCustomToken.connect(rootDeployerWallet).mint(rootTestWallet.address, ethers.utils.parseEther("1000.0").toBigInt()); + await waitForReceipt(resp.hash, rootProvider); + } + + if ((await rootBridge.largeTransferThresholds(rootCustomToken.address)).toString() != "0") { + console.log("Rate limiting has already been configured for custom token, skip."); + } else { + console.log("Set rate control..."); + // Set rate control + let resp = await rootBridge.connect(rootDeployerWallet).setRateControlThreshold( + rootCustomToken.address, + ethers.utils.parseEther("20016.0"), + ethers.utils.parseEther("5.56"), + ethers.utils.parseEther("10008.0") + ); + await waitForReceipt(resp.hash, rootProvider); + } + + // Revoke roles + if (!await rootBridge.hasRole(utils.keccak256(utils.toUtf8Bytes("RATE")), deployerAddr)) { + console.log("Deployer has already revoked RATE_CONTROL_ROLE..., skip."); + } else { + console.log("Revoke RATE_CONTROL_ROLE of deployer...") + let resp = await rootBridge.connect(rootDeployerWallet).revokeRole(utils.keccak256(utils.toUtf8Bytes("RATE")), deployerAddr); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + if (!await rootBridge.hasRole(await rootBridge.DEFAULT_ADMIN_ROLE(), deployerAddr)) { + console.log("Deployer has already revoked DEFAULT_ADMIN..., skip."); + } else { + console.log("Revoke DEFAULT_ADMIN of deployer...") + let resp = await rootBridge.connect(rootDeployerWallet).revokeRole(await rootBridge.DEFAULT_ADMIN_ROLE(), deployerAddr); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + // Print summary + console.log("Does multisig have DEFAULT_ADMIN: ", await rootBridge.hasRole(await rootBridge.DEFAULT_ADMIN_ROLE(), rootPrivilegedMultisig)); + console.log("Does deployer have DEFAULT_ADMIN: ", await rootBridge.hasRole(await rootBridge.DEFAULT_ADMIN_ROLE(), deployerAddr)); + console.log("Does multisig have RATE_ADMIN: ", await rootBridge.hasRole(utils.keccak256(utils.toUtf8Bytes("RATE")), rootPrivilegedMultisig)); + console.log("Does deployer have RATE_ADMIN: ", await rootBridge.hasRole(utils.keccak256(utils.toUtf8Bytes("RATE")), deployerAddr)); + + console.log("=======End Test Preparation======="); +} +run(); \ No newline at end of file diff --git a/scripts/bootstrap/README.md b/scripts/bootstrap/README.md index e5ca044e..e510bb20 100644 --- a/scripts/bootstrap/README.md +++ b/scripts/bootstrap/README.md @@ -3,12 +3,11 @@ ## Prerequisite 1. Coordinate with Axelar to obtain their admin address for initial funding as well as the desired amount in $IMX. (500 IMX in previous discussion). 2. Obtain the deployer account on both child chain and root chain. -3. Obtain the amount to fund deployer on child chain in $IMX. (500 IMX by default). -4. Coordinate with security to obtain the addresses for different roles. -5. Fund deployer with `ETH` and `IMX` on root chain. (As a rule of thumb, _0.1 ETH and 1100 IMX_ (TBD)). -6. Fund a test account with `ETH` and `IMX` on root chain. (As a rule of thumb, _0.1 ETH and 50 IMX_ (TBD)). -7. Fund a rate admin account with `ETH` on root chain. (As a rule of thumb, _0.1 ETH_ (TBD)). - +3. Obtain the nonce reserved deployer account and reserved nonce on both child chain and root chain. +4. Obtain the amount to fund reserved deployer on child chain in $IMX. (10 IMX by default). +5. Obtain the amount to fund deployer on child chain in $IMX. (500 IMX by default). +6. Fund deployer with `ETH` and `IMX` on root chain. (As a rule of thumb, _0.1 ETH and 1100 IMX_ (TBD)). +7. Fund a test account with `ETH` and `IMX` on root chain. (As a rule of thumb, _0.1 ETH and 50 IMX_ (TBD)). ## Bootstrapping 0. Install dependency and compile contracts (Run in root directory) @@ -23,77 +22,59 @@ cp .env.example .env ``` 2. Set the following environment variables ``` -# Set prior to 1_deployer_funding.js +# Set prior to 0_pre_validation.js +# Name of the child chain MUST match Axelar's definition. CHILD_CHAIN_NAME= +# The RPC URL of child chain. CHILD_RPC_URL= +# The chain ID of the child chain. CHILD_CHAIN_ID= +# Name of the root chain MUST match Axelar's definition. ROOT_CHAIN_NAME= +# The RPC URL of root chain. ROOT_RPC_URL= +# The chain ID of the root chain. ROOT_CHAIN_ID= -## The admin EOA address on the child chain. -CHILD_ADMIN_ADDR= -## The private key for the admin EOA or "ledger" if using hardware wallet. -CHILD_ADMIN_EOA_SECRET= -## The deployer address on child chain. -CHILD_DEPLOYER_ADDR= -## The private key for the deployer on child chain or "ledger" if using hardware wallet. -CHILD_DEPLOYER_SECRET= -## The amount of fund deployer required on L2, unit is in IMX or 10^18 Wei. -CHILD_DEPLOYER_FUND= -## The deployer address on root chain. -ROOT_DEPLOYER_ADDR= -## The private key for the deployer on root chain or "ledger" if using hardware wallet. -ROOT_DEPLOYER_SECRET= -## The private key for rate admin or "ledger" if using hardware wallet. -ROOT_BRIDGE_RATE_ADMIN_SECRET= +## The deployer address on child & root chains. +DEPLOYER_ADDR= +## The private key for the deployer on child & root chains or "ledger" if using hardware wallet. +DEPLOYER_SECRET= +## The ledger index for the deployer on child & root chains, required if using ledger. +DEPLOYER_LEDGER_INDEX= +## The nonce reserved deployer address on child & root chains. +NONCE_RESERVED_DEPLOYER_ADDR= +## The nonce reserved deployer, or "ledger" if using hardware wallet. +NONCE_RESERVED_DEPLOYER_SECRET= +## The ledger index for the nonce reserved deployer. +NONCE_RESERVED_DEPLOYER_INDEX= +## The reserved nonce for token template deployment. +NONCE_RESERVED= ## The IMX token address on root chain. ROOT_IMX_ADDR= ## The Wrapped ETH token address on the root chain. ROOT_WETH_ADDR= -## The Axelar address for receive initial funding on the child chain. +## The Axelar address to receive initial funding on the child chain. AXELAR_EOA= +## The passport nonce reserver +PASSPORT_NONCE_RESERVER_ADDR= ## The amount of fund Axelar requested, unit is in IMX or 10^18 Wei. AXELAR_FUND= +## The amount of fund deployer to be left with after bootstrapping on L2, unit is in IMX or 10^18 Wei. +CHILD_DEPLOYER_FUND= +## The amount of fund nonce reserved deployer required on L2, unit is in IMX or 10^18 Wei. +CHILD_NONCE_RESERVED_DEPLOYER_FUND= +## The amount of fund passport reserver required on L2, unit is in IMX or 10^18 Wei. +PASSPORT_NONCE_RESERVER_FUND= ## The maximum amount of IMX that can be deposited to L2, unit is in IMX or 10^18 Wei. IMX_DEPOSIT_LIMIT= -## The address to perform child bridge upgrade. -CHILD_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child bridge. -CHILD_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in child bridge. -CHILD_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in child bridge. -CHILD_BRIDGE_UNPAUSER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in child bridge. -CHILD_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child adaptor. -CHILD_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_TARGET_MANAGER= -## The address to perform root adaptor upgrade. -ROOT_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root bridge. -ROOT_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in root bridge. -ROOT_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in root bridge. -ROOT_BRIDGE_UNPAUSER= -## The address to be assigned with VARIABLE_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_VARIABLE_MANAGER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root adaptor. -ROOT_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_TARGET_MANAGER= +## The privileged transaction Multisig address on the root chain. +ROOT_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the root chain. +ROOT_BREAKGLASS_ADDR= +## The privileged transaction Multisig address on the child chain. +CHILD_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the child chain. +CHILD_BREAKGLASS_ADDR= ## The capacity of the rate limit policy of IMX token, unit is in 10^18. RATE_LIMIT_IMX_CAPACITY= ## The refill rate of the rate limit policy of IMX token, unit is in 10^18. @@ -139,55 +120,66 @@ RATE_LIMIT_GOG_REFILL_RATE= ## The large threshold of the rate limit policy of GOG token, unit is in 10^18. RATE_LIMIT_GOG_LARGE_THRESHOLD= ``` -3. Fund deployer +3. Pre validation +``` +npx ts-node 0_pre_validation.ts 2>&1 | tee bootstrap.out ``` -node 1_deployer_funding.js 2>&1 | tee bootstrap.out +4. Fund deployer ``` -4. Wait for Axelar to deploy & setup their system and Security team to deploy & setup multisig wallet. -5. Set the following environment variables +npx ts-node 1_deployer_funding.ts 2>&1 | tee bootstrap.out +``` +5. Wait for Axelar to deploy & setup their system and Security team to deploy & setup multisig wallet. +6. Set the following environment variables ``` CHILD_GATEWAY_ADDRESS= CHILD_GAS_SERVICE_ADDRESS= MULTISIG_CONTRACT_ADDRESS= ROOT_GATEWAY_ADDRESS= ROOT_GAS_SERVICE_ADDRESS= +## (Optional) to verify child contract after deployment +CHILD_CHAIN_BLOCKSCOUT_API_URL= +## (Optional) to verify root contract after deployment +ROOT_CHAIN_ETHERSCAN_API_KEY= ``` -6. Basic contract validation - +7. Basic contract validation If multisig is deployed: ``` -node 2_deployment_validation.js 2>&1 | tee -a bootstrap.out +npx ts-node 2_deployment_validation.ts 2>&1 | tee -a bootstrap.out ``` If multisig isn't deployed: ``` -SKIP_MULTISIG_CHECK=true node 2_deployment_validation.js 2>&1 | tee -a bootstrap.out +SKIP_MULTISIG_CHECK=true npx ts-node 2_deployment_validation.ts 2>&1 | tee -a bootstrap.out ``` -7. Deploy bridge contracts on child and root chain. +8. Deploy bridge contracts on child and root chain. ``` -node 3_child_deployment.js 2>&1 | tee -a bootstrap.out -node 4_root_deployment.js 2>&1 | tee -a bootstrap.out +npx ts-node 3_child_deployment.ts 2>&1 | tee -a bootstrap.out +npx ts-node 4_root_deployment.ts 2>&1 | tee -a bootstrap.out ``` -8. Initialise bridge contracts on child chain. +9. Initialise bridge contracts on child chain. ``` -node 5_child_initialisation.js 2>&1 | tee -a bootstrap.out +npx ts-node 5_child_initialisation.ts 2>&1 | tee -a bootstrap.out ``` -9. IMX Burning +10. IMX Burning ``` -node 6_imx_burning.js 2>&1 | tee -a bootstrap.out +npx ts-node 6_imx_burning.ts 2>&1 | tee -a bootstrap.out ``` -10. IMX Rebalancing +11. IMX Rebalancing ``` -node 7_imx_rebalancing.js 2>&1 | tee -a bootstrap.out +npx ts-node 7_imx_rebalancing.ts 2>&1 | tee -a bootstrap.out ``` -11. Initialise bridge contracts on root chain. +12. Initialise bridge contracts on root chain. ``` -node 8_root_initialisation.js 2>&1 | tee -a bootstrap.out +npx ts-node 8_root_initialisation.ts 2>&1 | tee -a bootstrap.out ``` -12. Set the following environment variable +13. Set the following environment variable ``` TEST_ACCOUNT_SECRET= ``` -13. Test bridge functions +14. Prepare for test +``` +npx ts-node 9_test_preparation.ts 2>&1 | tee -a bootstrap.out +``` +15. Test bridge functions ``` -npx mocha --require mocha-suppress-logs ../e2e/ 2>&1 | tee -a bootstrap.out +AXELAR_API_URL=${Axelar API URL} npx mocha --require mocha-suppress-logs ../e2e/e2e.ts 2>&1 | tee -a bootstrap.out ``` \ No newline at end of file diff --git a/scripts/deploy/.env.example b/scripts/deploy/.env.example index dd94dd60..3e5f83a2 100644 --- a/scripts/deploy/.env.example +++ b/scripts/deploy/.env.example @@ -1,64 +1,55 @@ -# Access to child and root chains. +# Name of the child chain MUST match Axelar's definition. CHILD_CHAIN_NAME= +# The RPC URL of child chain. CHILD_RPC_URL= +# The chain ID of the child chain. CHILD_CHAIN_ID= +# Name of the root chain MUST match Axelar's definition. ROOT_CHAIN_NAME= +# The RPC URL of root chain. ROOT_RPC_URL= +# The chain ID of the root chain. ROOT_CHAIN_ID= -## The admin EOA address on the child chain that is allowed to top up the child bridge contract. -CHILD_ADMIN_ADDR= -## The multisig contract that is allowed to top up the child bridge contract. -MULTISIG_CONTRACT_ADDRESS= -## The private key for the deployer on child chain or "ledger" if using hardware wallet. -CHILD_DEPLOYER_SECRET= -## The private key for the deployer on root chain or "ledger" if using hardware wallet. -ROOT_DEPLOYER_SECRET= -## The private key for rate admin or "ledger" if using hardware wallet. -ROOT_BRIDGE_RATE_ADMIN_SECRET= +## The deployer address on child & root chains. +DEPLOYER_ADDR= +## The private key for the deployer on child & root chains or "ledger" if using hardware wallet. +DEPLOYER_SECRET= +## The ledger index for the deployer on child & root chains, required if using ledger. +DEPLOYER_LEDGER_INDEX= +## The nonce reserved deployer address on child & root chains. +NONCE_RESERVED_DEPLOYER_ADDR= +## The nonce reserved deployer, or "ledger" if using hardware wallet. +NONCE_RESERVED_DEPLOYER_SECRET= +## The ledger index for the nonce reserved deployer. +NONCE_RESERVED_DEPLOYER_INDEX= +## The reserved nonce for token template deployment. +NONCE_RESERVED= ## The IMX token address on root chain. ROOT_IMX_ADDR= ## The Wrapped ETH token address on the root chain. ROOT_WETH_ADDR= +## The Axelar address to receive initial funding on the child chain. +AXELAR_EOA= +## The passport nonce reserver +PASSPORT_NONCE_RESERVER_ADDR= +## The amount of fund Axelar requested, unit is in IMX or 10^18 Wei. +AXELAR_FUND= +## The amount of fund deployer to be left with after bootstrapping on L2, unit is in IMX or 10^18 Wei. +CHILD_DEPLOYER_FUND= +## The amount of fund nonce reserved deployer required on L2, unit is in IMX or 10^18 Wei. +CHILD_NONCE_RESERVED_DEPLOYER_FUND= +## The amount of fund passport reserver required on L2, unit is in IMX or 10^18 Wei. +PASSPORT_NONCE_RESERVER_FUND= ## The maximum amount of IMX that can be deposited to L2, unit is in IMX or 10^18 Wei. IMX_DEPOSIT_LIMIT= -## The address to perform child bridge upgrade. -CHILD_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child bridge. -CHILD_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in child bridge. -CHILD_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in child bridge. -CHILD_BRIDGE_UNPAUSER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in child bridge. -CHILD_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child adaptor. -CHILD_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_TARGET_MANAGER= -## The address to perform root adaptor upgrade. -ROOT_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root bridge. -ROOT_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in root bridge. -ROOT_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in root bridge. -ROOT_BRIDGE_UNPAUSER= -## The address to be assigned with VARIABLE_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_VARIABLE_MANAGER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root adaptor. -ROOT_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_TARGET_MANAGER= +## The privileged transaction Multisig address on the root chain. +ROOT_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the root chain. +ROOT_BREAKGLASS_ADDR= +## The privileged transaction Multisig address on the child chain. +CHILD_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the child chain. +CHILD_BREAKGLASS_ADDR= ## The capacity of the rate limit policy of IMX token, unit is in 10^18. RATE_LIMIT_IMX_CAPACITY= ## The refill rate of the rate limit policy of IMX token, unit is in 10^18. diff --git a/scripts/deploy/README.md b/scripts/deploy/README.md index 6b1e2d8e..6884186f 100644 --- a/scripts/deploy/README.md +++ b/scripts/deploy/README.md @@ -12,66 +12,58 @@ cp .env.example .env 2. Set the following fields inside `.env` ``` # Access to child and root chains. +# Name of the child chain MUST match Axelar's definition. CHILD_CHAIN_NAME= +# The RPC URL of child chain. CHILD_RPC_URL= +# The chain ID of the child chain. CHILD_CHAIN_ID= +# Name of the root chain MUST match Axelar's definition. ROOT_CHAIN_NAME= +# The RPC URL of root chain. ROOT_RPC_URL= +# The chain ID of the root chain. ROOT_CHAIN_ID= -## The admin EOA address on the child chain that is allowed to top up the child bridge contract. -CHILD_ADMIN_ADDR= -## The multisig contract that is allowed to top up the child bridge contract. -MULTISIG_CONTRACT_ADDRESS= -## The private key for the deployer on child chain or "ledger" if using hardware wallet. -CHILD_DEPLOYER_SECRET= -## The private key for the deployer on root chain or "ledger" if using hardware wallet. -ROOT_DEPLOYER_SECRET= -## The private key for rate admin or "ledger" if using hardware wallet. -ROOT_BRIDGE_RATE_ADMIN_SECRET= +## The deployer address on child & root chains. +DEPLOYER_ADDR= +## The private key for the deployer on child & root chains or "ledger" if using hardware wallet. +DEPLOYER_SECRET= +## The ledger index for the deployer on child & root chains, required if using ledger. +DEPLOYER_LEDGER_INDEX= +## The nonce reserved deployer address on child & root chains. +NONCE_RESERVED_DEPLOYER_ADDR= +## The nonce reserved deployer, or "ledger" if using hardware wallet. +NONCE_RESERVED_DEPLOYER_SECRET= +## The ledger index for the nonce reserved deployer. +NONCE_RESERVED_DEPLOYER_INDEX= +## The reserved nonce for token template deployment. +NONCE_RESERVED= ## The IMX token address on root chain. ROOT_IMX_ADDR= ## The Wrapped ETH token address on the root chain. ROOT_WETH_ADDR= +## The Axelar address to receive initial funding on the child chain. +AXELAR_EOA= +## The passport nonce reserver +PASSPORT_NONCE_RESERVER_ADDR= +## The amount of fund Axelar requested, unit is in IMX or 10^18 Wei. +AXELAR_FUND= +## The amount of fund deployer to be left with after bootstrapping on L2, unit is in IMX or 10^18 Wei. +CHILD_DEPLOYER_FUND= +## The amount of fund nonce reserved deployer required on L2, unit is in IMX or 10^18 Wei. +CHILD_NONCE_RESERVED_DEPLOYER_FUND= +## The amount of fund passport reserver required on L2, unit is in IMX or 10^18 Wei. +PASSPORT_NONCE_RESERVER_FUND= ## The maximum amount of IMX that can be deposited to L2, unit is in IMX or 10^18 Wei. IMX_DEPOSIT_LIMIT= -## The address to perform child bridge upgrade. -CHILD_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child bridge. -CHILD_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in child bridge. -CHILD_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in child bridge. -CHILD_BRIDGE_UNPAUSER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in child bridge. -CHILD_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in child adaptor. -CHILD_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_TARGET_MANAGER= -## The address to perform root adaptor upgrade. -ROOT_PROXY_ADMIN= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root bridge. -ROOT_BRIDGE_DEFAULT_ADMIN= -## The address to be assigned with PAUSER_ROLE in root bridge. -ROOT_BRIDGE_PAUSER= -## The address to be assigned with UNPAUSER_ROLE in root bridge. -ROOT_BRIDGE_UNPAUSER= -## The address to be assigned with VARIABLE_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_VARIABLE_MANAGER= -## The address to be assigned with ADAPTOR_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_ADAPTOR_MANAGER= -## The address to be assigned with DEFAULT_ADMIN_ROLE in root adaptor. -ROOT_ADAPTOR_DEFAULT_ADMIN= -## The address to be assigned with BRIDGE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_BRIDGE_MANAGER= -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_GAS_SERVICE_MANAGER= -## The address to be assigned with TARGET_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_TARGET_MANAGER= +## The privileged transaction Multisig address on the root chain. +ROOT_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the root chain. +ROOT_BREAKGLASS_ADDR= +## The privileged transaction Multisig address on the child chain. +CHILD_PRIVILEGED_MULTISIG_ADDR= +# The break glass signer address on the child chain. +CHILD_BREAKGLASS_ADDR= ## The capacity of the rate limit policy of IMX token, unit is in 10^18. RATE_LIMIT_IMX_CAPACITY= ## The refill rate of the rate limit policy of IMX token, unit is in 10^18. @@ -128,5 +120,5 @@ ROOT_GAS_SERVICE_ADDRESS= 3. Deploy and setup contracts: ``` -node deployAndInit.js +npx ts-node deployAndInit.ts ``` \ No newline at end of file diff --git a/scripts/deploy/child_deployment.js b/scripts/deploy/child_deployment.js deleted file mode 100644 index 6065ceb0..00000000 --- a/scripts/deploy/child_deployment.js +++ /dev/null @@ -1,113 +0,0 @@ -// Deploy child contracts -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const { LedgerSigner } = require('@ethersproject/hardware-wallets') -const fs = require('fs'); - -exports.deployChildContracts = async () => { - // Check environment variables - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let childDeployerSecret = helper.requireEnv("CHILD_DEPLOYER_SECRET"); - let childGatewayAddr = helper.requireEnv("CHILD_GATEWAY_ADDRESS"); - let childProxyAdmin = helper.requireEnv("CHILD_PROXY_ADMIN"); - - // Get admin address - const childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - let adminWallet; - if (childDeployerSecret == "ledger") { - adminWallet = new LedgerSigner(childProvider); - } else { - adminWallet = new ethers.Wallet(childDeployerSecret, childProvider); - } - let adminAddr = await adminWallet.getAddress(); - console.log("Deployer address is: ", adminAddr); - - // Execute - console.log("Deploy child contracts in..."); - await helper.waitForConfirmation(); - - // Deploy child token template - let childTokenTemplateObj = JSON.parse(fs.readFileSync('../../out/ChildERC20.sol/ChildERC20.json', 'utf8')); - console.log("Deploy child token template..."); - let childTokenTemplate = await helper.deployChildContract(childTokenTemplateObj, adminWallet); - console.log("Transaction submitted: ", JSON.stringify(childTokenTemplate.deployTransaction, null, 2)); - await helper.waitForReceipt(childTokenTemplate.deployTransaction.hash, childProvider); - // Initialise template - console.log("Initialise child token template..."); - let [priorityFee, maxFee] = await helper.getFee(adminWallet); - let resp = await childTokenTemplate.connect(adminWallet).initialize("000000000000000000000000000000000000007B", "TEMPLATE", "TPT", 18, { - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, childProvider); - console.log("Deployed to CHILD_TOKEN_TEMPLATE: ", childTokenTemplate.address); - - // Deploy wrapped IMX - let wrappedIMXObj = JSON.parse(fs.readFileSync('../../out/WIMX.sol/WIMX.json', 'utf8')); - console.log("Deploy wrapped IMX..."); - let wrappedIMX = await helper.deployChildContract(wrappedIMXObj, adminWallet); - console.log("Transaction submitted: ", JSON.stringify(wrappedIMX.deployTransaction, null, 2)); - await helper.waitForReceipt(wrappedIMX.deployTransaction.hash, childProvider); - console.log("Deployed to WRAPPED_IMX_ADDRESS: ", wrappedIMX.address); - - // Deploy proxy admin - let proxyAdminObj = JSON.parse(fs.readFileSync('../../out/ProxyAdmin.sol/ProxyAdmin.json', 'utf8')); - console.log("Deploy proxy admin..."); - let proxyAdmin = await helper.deployChildContract(proxyAdminObj, adminWallet); - console.log("Transaction submitted: ", JSON.stringify(proxyAdmin.deployTransaction, null, 2)); - await helper.waitForReceipt(proxyAdmin.deployTransaction.hash, childProvider); - // Change owner - console.log("Change ownership..."); - [priorityFee, maxFee] = await helper.getFee(adminWallet); - resp = await proxyAdmin.connect(adminWallet).transferOwnership(childProxyAdmin, { - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, childProvider); - console.log("Deployed to CHILD_PROXY_ADMIN: ", proxyAdmin.address); - - // Deploy child bridge impl - let childBridgeImplObj = JSON.parse(fs.readFileSync('../../out/ChildERC20Bridge.sol/ChildERC20Bridge.json', 'utf8')); - console.log("Deploy child bridge impl..."); - let childBridgeImpl = await helper.deployChildContract(childBridgeImplObj, adminWallet, adminWallet.address); - console.log("Transaction submitted: ", JSON.stringify(childBridgeImpl.deployTransaction, null, 2)); - await helper.waitForReceipt(childBridgeImpl.deployTransaction.hash, childProvider); - console.log("Deployed to CHILD_BRIDGE_IMPL_ADDRESS: ", childBridgeImpl.address); - - // Deploy child bridge proxy - let childBridgeProxyObj = JSON.parse(fs.readFileSync('../../out/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json', 'utf8')); - console.log("Deploy child bridge proxy..."); - let childBridgeProxy = await helper.deployChildContract(childBridgeProxyObj, adminWallet, childBridgeImpl.address, proxyAdmin.address, []); - console.log("Transaction submitted: ", JSON.stringify(childBridgeProxy.deployTransaction, null, 2)); - await helper.waitForReceipt(childBridgeProxy.deployTransaction.hash, childProvider); - console.log("Deployed to CHILD_BRIDGE_PROXY_ADDRESS: ", childBridgeProxy.address); - - // Deploy child adaptor impl - let childAdaptorImplObj = JSON.parse(fs.readFileSync('../../out/ChildAxelarBridgeAdaptor.sol/ChildAxelarBridgeAdaptor.json', 'utf8')); - console.log("Deploy child adaptor impl..."); - let childAdaptorImpl = await helper.deployChildContract(childAdaptorImplObj, adminWallet, childGatewayAddr, adminWallet.address); - console.log("Transaction submitted: ", JSON.stringify(childAdaptorImpl.deployTransaction, null, 2)); - await helper.waitForReceipt(childAdaptorImpl.deployTransaction.hash, childProvider); - console.log("Deployed to CHILD_ADAPTOR_IMPL_ADDRESS: ", childAdaptorImpl.address); - - // Deploy child adaptor proxy - let childAdaptorProxyObj = JSON.parse(fs.readFileSync('../../out/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json', 'utf8')); - console.log("Deploy child adaptor proxy..."); - let childAdaptorProxy = await helper.deployChildContract(childAdaptorProxyObj, adminWallet, childAdaptorImpl.address, proxyAdmin.address, []); - console.log("Transaction submitted: ", JSON.stringify(childAdaptorProxy.deployTransaction, null, 2)); - await helper.waitForReceipt(childAdaptorProxy.deployTransaction.hash, childProvider); - console.log("Deployed to CHILD_ADAPTOR_PROXY_ADDRESS: ", childAdaptorProxy.address); - - let contractData = { - CHILD_BRIDGE_ADDRESS: childBridgeProxy.address, - CHILD_ADAPTOR_ADDRESS: childAdaptorProxy.address, - WRAPPED_IMX_ADDRESS: wrappedIMX.address, - CHILD_TOKEN_TEMPLATE: childTokenTemplate.address, - }; - fs.writeFileSync(".child.bridge.contracts.json", JSON.stringify(contractData, null, 2)); -} \ No newline at end of file diff --git a/scripts/deploy/child_deployment.ts b/scripts/deploy/child_deployment.ts new file mode 100644 index 00000000..c62f3b42 --- /dev/null +++ b/scripts/deploy/child_deployment.ts @@ -0,0 +1,205 @@ +// Deploy child contracts +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, waitForConfirmation, deployChildContract, waitForReceipt, getFee, getChildContracts, getContract, saveChildContracts, verifyChildContract } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +export async function deployChildContracts() { + // Check environment variables + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let nonceReservedDeployerSecret = requireEnv("NONCE_RESERVED_DEPLOYER_SECRET"); + let nonceReserved = Number(requireEnv("NONCE_RESERVED")); + let childGatewayAddr = requireEnv("CHILD_GATEWAY_ADDRESS"); + + // Read from contract file. + let childContracts = getChildContracts(); + + const childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + + // Get deployer address + let childDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + childDeployerWallet = new LedgerSigner(childProvider, derivationPath); + } else { + childDeployerWallet = new ethers.Wallet(deployerSecret, childProvider); + } + let deployerAddr = await childDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); + + // Execute + console.log("Deploy child contracts in..."); + await waitForConfirmation(); + + // Deploy wrapped IMX + let wrappedIMX; + if (childContracts.WRAPPED_IMX_ADDRESS != "") { + console.log("Wrapped IMX has already been deployed to: " + childContracts.WRAPPED_IMX_ADDRESS + ", skip."); + wrappedIMX = getContract("WIMX", childContracts.WRAPPED_IMX_ADDRESS, childProvider); + } else { + console.log("Deploy wrapped IMX..."); + wrappedIMX = await deployChildContract("WIMX", childDeployerWallet, null); + console.log("Transaction submitted: ", JSON.stringify(wrappedIMX.deployTransaction, null, 2)); + await waitForReceipt(wrappedIMX.deployTransaction.hash, childProvider); + childContracts.WRAPPED_IMX_ADDRESS = wrappedIMX.address; + saveChildContracts(childContracts); + console.log("Deployed to WRAPPED_IMX_ADDRESS: ", wrappedIMX.address); + await verifyChildContract("WIMX", wrappedIMX.address); + } + + // Deploy child bridge impl + let childBridgeImpl; + if (childContracts.CHILD_BRIDGE_IMPL_ADDRESS != "") { + console.log("Child bridge impl has already been deployed to: " + childContracts.CHILD_BRIDGE_IMPL_ADDRESS + ", skip."); + childBridgeImpl = getContract("ChildERC20Bridge", childContracts.CHILD_BRIDGE_IMPL_ADDRESS, childProvider); + } else { + console.log("Deploy child bridge impl..."); + childBridgeImpl = await deployChildContract("ChildERC20Bridge", childDeployerWallet, null, deployerAddr); + console.log("Transaction submitted: ", JSON.stringify(childBridgeImpl.deployTransaction, null, 2)); + await waitForReceipt(childBridgeImpl.deployTransaction.hash, childProvider); + childContracts.CHILD_BRIDGE_IMPL_ADDRESS = childBridgeImpl.address; + saveChildContracts(childContracts); + console.log("Deployed to CHILD_BRIDGE_IMPL_ADDRESS: ", childBridgeImpl.address); + await verifyChildContract("ChildERC20Bridge", childBridgeImpl.address); + } + + // Deploy child adaptor impl + let childAdaptorImpl; + if (childContracts.CHILD_ADAPTOR_IMPL_ADDRESS != "") { + console.log("Child adaptor impl has already been deployed to: " + childContracts.CHILD_ADAPTOR_IMPL_ADDRESS + ", skip."); + childAdaptorImpl = getContract("ChildAxelarBridgeAdaptor", childContracts.CHILD_ADAPTOR_IMPL_ADDRESS, childProvider); + } else { + console.log("Deploy child adaptor impl..."); + childAdaptorImpl = await deployChildContract("ChildAxelarBridgeAdaptor", childDeployerWallet, null, childGatewayAddr, deployerAddr); + console.log("Transaction submitted: ", JSON.stringify(childAdaptorImpl.deployTransaction, null, 2)); + await waitForReceipt(childAdaptorImpl.deployTransaction.hash, childProvider); + childContracts.CHILD_ADAPTOR_IMPL_ADDRESS = childAdaptorImpl.address; + saveChildContracts(childContracts); + console.log("Deployed to CHILD_ADAPTOR_IMPL_ADDRESS: ", childAdaptorImpl.address); + await verifyChildContract("ChildAxelarBridgeAdaptor", childAdaptorImpl.address); + } + + if (childDeployerWallet instanceof LedgerSigner) { + childDeployerWallet.close(); + } + + // Get reserved wallet + let reservedDeployerWallet; + if (nonceReservedDeployerSecret == "ledger") { + let index = requireEnv("NONCE_RESERVED_DEPLOYER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + reservedDeployerWallet = new LedgerSigner(childProvider, derivationPath); + } else { + reservedDeployerWallet = new ethers.Wallet(nonceReservedDeployerSecret, childProvider); + } + let reservedDeployerAddr = await reservedDeployerWallet.getAddress(); + console.log("Reserved deployer address is: ", reservedDeployerAddr); + + // Deploy child token template + let childTokenTemplate; + if (childContracts.CHILD_TOKEN_TEMPLATE != "") { + console.log("Child token template has already been deployed to: " + childContracts.CHILD_TOKEN_TEMPLATE + ", skip."); + childTokenTemplate = getContract("ChildERC20", childContracts.CHILD_TOKEN_TEMPLATE, childProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await childProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved != currentNonce) { + throw("Nonce mismatch, expected " + nonceReserved + " actual " + currentNonce); + } + console.log("Deploy child token template..."); + childTokenTemplate = await deployChildContract("ChildERC20", reservedDeployerWallet, nonceReserved); + console.log("Transaction submitted: ", JSON.stringify(childTokenTemplate.deployTransaction, null, 2)); + await waitForReceipt(childTokenTemplate.deployTransaction.hash, childProvider); + childContracts.CHILD_TOKEN_TEMPLATE = childTokenTemplate.address; + saveChildContracts(childContracts); + console.log("Deployed to CHILD_TOKEN_TEMPLATE: ", childTokenTemplate.address); + await verifyChildContract("ChildERC20", childTokenTemplate.address); + } + + // Initialise template + if (await childTokenTemplate.name() == "TEMPLATE") { + console.log("Child token template has already been initialised, skip."); + } else { + console.log("Initialise child token template..."); + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childTokenTemplate.connect(reservedDeployerWallet).initialize("000000000000000000000000000000000000007B", "TEMPLATE", "TPT", 18, { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, childProvider); + console.log("Initialised CHILD_TOKEN_TEMPLATE at: ", childTokenTemplate.address); + } + + // Deploy proxy admin + let proxyAdmin; + if (childContracts.CHILD_PROXY_ADMIN != "") { + console.log("Proxy admin has already been deployed to: " + childContracts.CHILD_PROXY_ADMIN + ", skip."); + proxyAdmin = getContract("ProxyAdmin", childContracts.CHILD_PROXY_ADMIN, childProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await childProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved + 2 != currentNonce) { + throw("Nonce mismatch, expected " + (nonceReserved + 2) + " actual " + currentNonce); + } + console.log("Deploy proxy admin..."); + proxyAdmin = await deployChildContract("ProxyAdmin", reservedDeployerWallet, null); + console.log("Transaction submitted: ", JSON.stringify(proxyAdmin.deployTransaction, null, 2)); + await waitForReceipt(proxyAdmin.deployTransaction.hash, childProvider); + childContracts.CHILD_PROXY_ADMIN = proxyAdmin.address; + saveChildContracts(childContracts); + console.log("Deployed to CHILD_PROXY_ADMIN: ", proxyAdmin.address); + await verifyChildContract("ProxyAdmin", proxyAdmin.address); + } + + // Deploy child bridge proxy + let childBridgeProxy; + if (childContracts.CHILD_BRIDGE_PROXY_ADDRESS != "") { + console.log("Child bridge proxy has already been deployed to: " + childContracts.CHILD_BRIDGE_PROXY_ADDRESS + ", skip."); + childBridgeProxy = getContract("TransparentUpgradeableProxy", childContracts.CHILD_BRIDGE_PROXY_ADDRESS, childProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await childProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved + 3 != currentNonce) { + throw("Nonce mismatch, expected " + (nonceReserved + 3) + " actual " + currentNonce); + } + console.log("Deploy child bridge proxy..."); + childBridgeProxy = await deployChildContract("TransparentUpgradeableProxy", reservedDeployerWallet, null, childBridgeImpl.address, proxyAdmin.address, []); + console.log("Transaction submitted: ", JSON.stringify(childBridgeProxy.deployTransaction, null, 2)); + await waitForReceipt(childBridgeProxy.deployTransaction.hash, childProvider); + childContracts.CHILD_BRIDGE_PROXY_ADDRESS = childBridgeProxy.address; + saveChildContracts(childContracts); + console.log("Deployed to CHILD_BRIDGE_PROXY_ADDRESS: ", childBridgeProxy.address); + await verifyChildContract("TransparentUpgradeableProxy", childBridgeProxy.address); + } + + // Deploy child adaptor proxy + let childAdaptorProxy; + if (childContracts.CHILD_ADAPTOR_PROXY_ADDRESS != "") { + console.log("Child adaptor proxy has already been deployed to: " + childContracts.CHILD_ADAPTOR_PROXY_ADDRESS + ", skip."); + childAdaptorProxy = getContract("TransparentUpgradeableProxy", childContracts.CHILD_ADAPTOR_PROXY_ADDRESS, childProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await childProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved + 4 != currentNonce) { + throw("Nonce mismatch, expected " + (nonceReserved + 4) + " actual " + currentNonce); + } + console.log("Deploy child adaptor proxy..."); + childAdaptorProxy = await deployChildContract("TransparentUpgradeableProxy", reservedDeployerWallet, null, childAdaptorImpl.address, proxyAdmin.address, []); + console.log("Transaction submitted: ", JSON.stringify(childAdaptorProxy.deployTransaction, null, 2)); + await waitForReceipt(childAdaptorProxy.deployTransaction.hash, childProvider); + childContracts.CHILD_ADAPTOR_PROXY_ADDRESS = childAdaptorProxy.address; + saveChildContracts(childContracts); + console.log("Deployed to CHILD_ADAPTOR_PROXY_ADDRESS: ", childAdaptorProxy.address); + await verifyChildContract("TransparentUpgradeableProxy", childAdaptorProxy.address); + } + + childContracts.CHILD_BRIDGE_ADDRESS = childBridgeProxy.address; + childContracts.CHILD_ADAPTOR_ADDRESS = childAdaptorProxy.address, + saveChildContracts(childContracts); +} \ No newline at end of file diff --git a/scripts/deploy/child_initialisation.js b/scripts/deploy/child_initialisation.js deleted file mode 100644 index 2aed4ce7..00000000 --- a/scripts/deploy/child_initialisation.js +++ /dev/null @@ -1,100 +0,0 @@ -// Initialise child contracts -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const { LedgerSigner } = require('@ethersproject/hardware-wallets') -const fs = require('fs'); - -exports.initialiseChildContracts = async () => { - let rootChainName = helper.requireEnv("ROOT_CHAIN_NAME"); - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let adminEOAAddr = helper.requireEnv("CHILD_ADMIN_ADDR"); - let childBridgeDefaultAdmin = helper.requireEnv("CHILD_BRIDGE_DEFAULT_ADMIN"); - let childBridgePauser = helper.requireEnv("CHILD_BRIDGE_PAUSER"); - let childBridgeUnpauser = helper.requireEnv("CHILD_BRIDGE_UNPAUSER"); - let childBridgeAdaptorManager = helper.requireEnv("CHILD_BRIDGE_ADAPTOR_MANAGER"); - let childAdaptorDefaultAdmin = helper.requireEnv("CHILD_ADAPTOR_DEFAULT_ADMIN"); - let childAdaptorBridgeManager = helper.requireEnv("CHILD_ADAPTOR_BRIDGE_MANAGER"); - let childAdaptorGasServiceManager = helper.requireEnv("CHILD_ADAPTOR_GAS_SERVICE_MANAGER"); - let childAdaptorTargetManager = helper.requireEnv("CHILD_ADAPTOR_TARGET_MANAGER"); - let childDeployerSecret = helper.requireEnv("CHILD_DEPLOYER_SECRET"); - let childGasServiceAddr = helper.requireEnv("CHILD_GAS_SERVICE_ADDRESS"); - let multisigAddr = helper.requireEnv("MULTISIG_CONTRACT_ADDRESS"); - let rootIMXAddr = helper.requireEnv("ROOT_IMX_ADDR"); - - // Read from contract file. - let data = fs.readFileSync(".child.bridge.contracts.json", 'utf-8'); - let childContracts = JSON.parse(data); - let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; - let childAdaptorAddr = childContracts.CHILD_ADAPTOR_ADDRESS; - let childWIMXAddr = childContracts.WRAPPED_IMX_ADDRESS; - let childTemplateAddr = childContracts.CHILD_TOKEN_TEMPLATE; - data = fs.readFileSync(".root.bridge.contracts.json", 'utf-8'); - let rootContracts = JSON.parse(data); - let rootAdaptorAddr = rootContracts.ROOT_ADAPTOR_ADDRESS; - - // Get admin address - const childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - let adminWallet; - if (childDeployerSecret == "ledger") { - adminWallet = new LedgerSigner(childProvider); - } else { - adminWallet = new ethers.Wallet(childDeployerSecret, childProvider); - } - let adminAddr = await adminWallet.getAddress(); - console.log("Deployer address is: ", adminAddr); - - // Execute - console.log("Initialise child contracts in..."); - await helper.waitForConfirmation(); - - // Initialise child bridge - let childBridgeObj = JSON.parse(fs.readFileSync('../../out/ChildERC20Bridge.sol/ChildERC20Bridge.json', 'utf8')); - console.log("Initialise child bridge..."); - let childBridge = new ethers.Contract(childBridgeAddr, childBridgeObj.abi, childProvider); - let [priorityFee, maxFee] = await helper.getFee(adminWallet); - let resp = await childBridge.connect(adminWallet).initialize( - { - defaultAdmin: childBridgeDefaultAdmin, - pauser: childBridgePauser, - unpauser: childBridgeUnpauser, - adaptorManager: childBridgeAdaptorManager, - initialDepositor: adminEOAAddr, - treasuryManager: multisigAddr, - }, - childAdaptorAddr, - childTemplateAddr, - rootIMXAddr, - childWIMXAddr, - { - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, childProvider); - - // Initialise child adaptor - let childAdaptorObj = JSON.parse(fs.readFileSync('../../out/ChildAxelarBridgeAdaptor.sol/ChildAxelarBridgeAdaptor.json', 'utf8')); - console.log("Initialise child adaptor..."); - let childAdaptor = new ethers.Contract(childAdaptorAddr, childAdaptorObj.abi, childProvider); - [priorityFee, maxFee] = await helper.getFee(adminWallet); - resp = await childAdaptor.connect(adminWallet).initialize( - { - defaultAdmin: childAdaptorDefaultAdmin, - bridgeManager: childAdaptorBridgeManager, - gasServiceManager: childAdaptorGasServiceManager, - targetManager: childAdaptorTargetManager, - }, - childBridgeAddr, - rootChainName, - rootAdaptorAddr, - childGasServiceAddr, - { - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, childProvider); -} \ No newline at end of file diff --git a/scripts/deploy/child_initialisation.ts b/scripts/deploy/child_initialisation.ts new file mode 100644 index 00000000..50154a7a --- /dev/null +++ b/scripts/deploy/child_initialisation.ts @@ -0,0 +1,106 @@ +// Initialise child contracts +'use strict'; +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, waitForConfirmation, waitForReceipt, getFee, getContract, getChildContracts, getRootContracts } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +export async function initialiseChildContracts() { + let rootChainName = requireEnv("ROOT_CHAIN_NAME"); + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let childGasServiceAddr = requireEnv("CHILD_GAS_SERVICE_ADDRESS"); + let multisigAddr = requireEnv("MULTISIG_CONTRACT_ADDRESS"); + let rootIMXAddr = requireEnv("ROOT_IMX_ADDR"); + let childPrivilegedMultisig = requireEnv("CHILD_PRIVILEGED_MULTISIG_ADDR"); + let childBreakglass = requireEnv("CHILD_BREAKGLASS_ADDR"); + + // Read from contract file. + let childContracts = getChildContracts(); + let rootContracts = getRootContracts(); + let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; + let childAdaptorAddr = childContracts.CHILD_ADAPTOR_ADDRESS; + let childWIMXAddr = childContracts.WRAPPED_IMX_ADDRESS; + let childTemplateAddr = childContracts.CHILD_TOKEN_TEMPLATE; + let rootAdaptorAddr = rootContracts.ROOT_ADAPTOR_ADDRESS; + let rootTemplateAddr = rootContracts.ROOT_TOKEN_TEMPLATE; + + // Root token template must have the same address as child token template + if (childTemplateAddr != rootTemplateAddr) { + throw("Token template contract address mismatch: root " + rootTemplateAddr + " child " + childTemplateAddr); + } + + // Get deployer address + const childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + let childDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + childDeployerWallet = new LedgerSigner(childProvider, derivationPath); + } else { + childDeployerWallet = new ethers.Wallet(deployerSecret, childProvider); + } + let deployerAddr = await childDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); + + // Execute + console.log("Initialise child contracts in..."); + await waitForConfirmation(); + + // Initialise child bridge + let childBridge = getContract("ChildERC20Bridge", childBridgeAddr, childProvider); + if (await childBridge.rootIMXToken() != "0x0000000000000000000000000000000000000000") { + console.log("Child bridge has already been initialised, skip."); + } else { + console.log("Initialise child bridge..."); + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childBridge.connect(childDeployerWallet).initialize( + { + defaultAdmin: childPrivilegedMultisig, + pauser: childBreakglass, + unpauser: childPrivilegedMultisig, + adaptorManager: childPrivilegedMultisig, + initialDepositor: deployerAddr, + treasuryManager: multisigAddr, + }, + childAdaptorAddr, + childTemplateAddr, + rootIMXAddr, + childWIMXAddr, + { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, childProvider); + } + + // Initialise child adaptor + let childAdaptor = getContract("ChildAxelarBridgeAdaptor", childAdaptorAddr, childProvider); + if (await childAdaptor.gasService() != "0x0000000000000000000000000000000000000000") { + console.log("Child adaptor has already been initialized, skip."); + } else { + console.log("Initialise child adaptor..."); + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childAdaptor.connect(childDeployerWallet).initialize( + { + defaultAdmin: childPrivilegedMultisig, + bridgeManager: childPrivilegedMultisig, + gasServiceManager: childPrivilegedMultisig, + targetManager: childPrivilegedMultisig, + }, + childBridgeAddr, + rootChainName, + rootAdaptorAddr, + childGasServiceAddr, + { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, childProvider); + } +} \ No newline at end of file diff --git a/scripts/deploy/deployAndInit.js b/scripts/deploy/deployAndInit.js deleted file mode 100644 index 78243245..00000000 --- a/scripts/deploy/deployAndInit.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; -require('dotenv').config(); -const deployChild = require("./child_deployment.js"); -const initChild = require("./child_initialisation.js"); -const deployRoot = require("./root_deployment.js"); -const initRoot = require("./root_initialisation.js"); - -async function run() { - await deployChild.deployChildContracts(); - await deployRoot.deployRootContracts(); - await initChild.initialiseChildContracts(); - await initRoot.initialiseRootContracts(); -} -run(); \ No newline at end of file diff --git a/scripts/deploy/deployAndInit.ts b/scripts/deploy/deployAndInit.ts new file mode 100644 index 00000000..b34a0cd8 --- /dev/null +++ b/scripts/deploy/deployAndInit.ts @@ -0,0 +1,14 @@ +'use strict'; +require('dotenv').config(); +import { deployChildContracts } from "./child_deployment"; +import { initialiseChildContracts } from "./child_initialisation"; +import { deployRootContracts } from "./root_deployment"; +import { initialiseRootContracts } from "./root_initialisation"; + +async function run() { + await deployChildContracts(); + await deployRootContracts(); + await initialiseChildContracts(); + await initialiseRootContracts(); +} +run(); \ No newline at end of file diff --git a/scripts/deploy/root_deployment.js b/scripts/deploy/root_deployment.js deleted file mode 100644 index 730c5d9c..00000000 --- a/scripts/deploy/root_deployment.js +++ /dev/null @@ -1,96 +0,0 @@ -// Deploy root contracts -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const { LedgerSigner } = require('@ethersproject/hardware-wallets') -const fs = require('fs'); - -exports.deployRootContracts = async () => { - // Check environment variables - let rootRPCURL = helper.requireEnv("ROOT_RPC_URL"); - let rootChainID = helper.requireEnv("ROOT_CHAIN_ID"); - let rootDeployerSecret = helper.requireEnv("ROOT_DEPLOYER_SECRET"); - let rootProxyAdmin = helper.requireEnv("ROOT_PROXY_ADMIN"); - let rootGatewayAddr = helper.requireEnv("ROOT_GATEWAY_ADDRESS"); - - // Get admin address - const rootProvider = new ethers.providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); - let adminWallet; - if (rootDeployerSecret == "ledger") { - adminWallet = new LedgerSigner(rootProvider); - } else { - adminWallet = new ethers.Wallet(rootDeployerSecret, rootProvider); - } - let adminAddr = await adminWallet.getAddress(); - console.log("Deployer address is: ", adminAddr); - - // Execute - console.log("Deploy root contracts in..."); - await helper.waitForConfirmation(); - - // Deploy root token template - let rootTokenTemplateObj = JSON.parse(fs.readFileSync('../../out/ChildERC20.sol/ChildERC20.json', 'utf8')); - console.log("Deploy root token template..."); - let rootTokenTemplate = await helper.deployRootContract(rootTokenTemplateObj, adminWallet); - console.log("Transaction submitted: ", JSON.stringify(rootTokenTemplate.deployTransaction, null, 2)); - await helper.waitForReceipt(rootTokenTemplate.deployTransaction.hash, rootProvider); - // Initialise template - console.log("Initialise root token template..."); - let resp = await rootTokenTemplate.connect(adminWallet).initialize("000000000000000000000000000000000000007B", "TEMPLATE", "TPT", 18); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - console.log("Deployed to ROOT_TOKEN_TEMPLATE: ", rootTokenTemplate.address); - - // Deploy proxy admin - let proxyAdminObj = JSON.parse(fs.readFileSync('../../out/ProxyAdmin.sol/ProxyAdmin.json', 'utf8')); - console.log("Deploy proxy admin..."); - let proxyAdmin = await helper.deployRootContract(proxyAdminObj, adminWallet); - console.log("Transaction submitted: ", JSON.stringify(proxyAdmin.deployTransaction, null, 2)); - await helper.waitForReceipt(proxyAdmin.deployTransaction.hash, rootProvider); - // Change owner - console.log("Change ownership...") - resp = await proxyAdmin.connect(adminWallet).transferOwnership(rootProxyAdmin); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - console.log("Deployed to ROOT_PROXY_ADMIN: ", proxyAdmin.address); - - // Deploy root bridge impl - let rootBridgeImplObj = JSON.parse(fs.readFileSync('../../out/RootERC20BridgeFlowRate.sol/RootERC20BridgeFlowRate.json', 'utf8')); - console.log("Deploy root bridge impl..."); - let rootBridgeImpl = await helper.deployRootContract(rootBridgeImplObj, adminWallet, adminWallet.address); - console.log("Transaction submitted: ", JSON.stringify(rootBridgeImpl.deployTransaction, null, 2)); - await helper.waitForReceipt(rootBridgeImpl.deployTransaction.hash, rootProvider); - console.log("Deployed to ROOT_BRIDGE_IMPL_ADDRESS: ", rootBridgeImpl.address); - - // Deploy root bridge proxy - let rootBridgeProxyObj = JSON.parse(fs.readFileSync('../../out/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json', 'utf8')); - console.log("Deploy root bridge proxy..."); - let rootBridgeProxy = await helper.deployRootContract(rootBridgeProxyObj, adminWallet, rootBridgeImpl.address, proxyAdmin.address, []); - console.log("Transaction submitted: ", JSON.stringify(rootBridgeProxy.deployTransaction, null, 2)); - await helper.waitForReceipt(rootBridgeProxy.deployTransaction.hash, rootProvider); - console.log("Deployed to ROOT_BRIDGE_PROXY_ADDRESS: ", rootBridgeProxy.address); - - // Deploy root adaptor impl - let rootAdaptorImplObj = JSON.parse(fs.readFileSync('../../out/RootAxelarBridgeAdaptor.sol/RootAxelarBridgeAdaptor.json', 'utf8')); - console.log("Deploy root adaptor impl..."); - let rootAdaptorImpl = await helper.deployRootContract(rootAdaptorImplObj, adminWallet, rootGatewayAddr, adminWallet.address); - console.log("Transaction submitted: ", JSON.stringify(rootAdaptorImpl.deployTransaction, null, 2)); - await helper.waitForReceipt(rootAdaptorImpl.deployTransaction.hash, rootProvider); - console.log("Deployed to ROOT_ADAPTOR_IMPL_ADDRESS: ", rootAdaptorImpl.address); - - // Deploy root adaptor proxy - let rootAdaptorProxyObj = JSON.parse(fs.readFileSync('../../out/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json', 'utf8')); - console.log("Deploy root adaptor proxy..."); - let rootAdaptorProxy = await helper.deployRootContract(rootAdaptorProxyObj, adminWallet, rootAdaptorImpl.address, proxyAdmin.address, []); - console.log("Transaction submitted: ", JSON.stringify(rootAdaptorProxy.deployTransaction, null, 2)); - await helper.waitForReceipt(rootAdaptorProxy.deployTransaction.hash, rootProvider); - console.log("Deployed to ROOT_ADAPTOR_PROXY_ADDRESS: ", rootAdaptorProxy.address); - - let contractData = { - ROOT_BRIDGE_ADDRESS: rootBridgeProxy.address, - ROOT_ADAPTOR_ADDRESS: rootAdaptorProxy.address, - ROOT_TOKEN_TEMPLATE: rootTokenTemplate.address, - }; - fs.writeFileSync(".root.bridge.contracts.json", JSON.stringify(contractData, null, 2)); -} \ No newline at end of file diff --git a/scripts/deploy/root_deployment.ts b/scripts/deploy/root_deployment.ts new file mode 100644 index 00000000..a519d8e5 --- /dev/null +++ b/scripts/deploy/root_deployment.ts @@ -0,0 +1,185 @@ +// Deploy root contracts +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers } from "ethers"; +import { requireEnv, waitForConfirmation, deployRootContract, waitForReceipt, getRootContracts, getContract, saveRootContracts, verifyRootContract } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +export async function deployRootContracts() { + // Check environment variables + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let nonceReservedDeployerSecret = requireEnv("NONCE_RESERVED_DEPLOYER_SECRET"); + let nonceReserved = Number(requireEnv("NONCE_RESERVED")); + let rootGatewayAddr = requireEnv("ROOT_GATEWAY_ADDRESS"); + + // Read from contract file. + let rootContracts = getRootContracts(); + + const rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); + + // Get deployer address + let rootDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + rootDeployerWallet = new LedgerSigner(rootProvider, derivationPath); + } else { + rootDeployerWallet = new ethers.Wallet(deployerSecret, rootProvider); + } + let deployerAddr = await rootDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); + + // Execute + console.log("Deploy root contracts in..."); + await waitForConfirmation(); + + // Deploy root bridge impl + let rootBridgeImpl; + if (rootContracts.ROOT_BRIDGE_IMPL_ADDRESS != "") { + console.log("Root bridge impl has already been deployed to: " + rootContracts.ROOT_BRIDGE_IMPL_ADDRESS + ", skip."); + rootBridgeImpl = getContract("RootERC20BridgeFlowRate", rootContracts.ROOT_BRIDGE_IMPL_ADDRESS, rootProvider); + } else { + console.log("Deploy root bridge impl..."); + rootBridgeImpl = await deployRootContract("RootERC20BridgeFlowRate", rootDeployerWallet, null, deployerAddr); + console.log("Transaction submitted: ", JSON.stringify(rootBridgeImpl.deployTransaction, null, 2)); + await waitForReceipt(rootBridgeImpl.deployTransaction.hash, rootProvider); + rootContracts.ROOT_BRIDGE_IMPL_ADDRESS = rootBridgeImpl.address; + saveRootContracts(rootContracts); + console.log("Deployed to ROOT_BRIDGE_IMPL_ADDRESS: ", rootBridgeImpl.address); + await verifyRootContract("RootERC20BridgeFlowRate", rootBridgeImpl.address, `"constructor(address)" "${deployerAddr}"`); + } + + // Deploy root adaptor impl + let rootAdaptorImpl; + if (rootContracts.ROOT_ADAPTOR_IMPL_ADDRESS != "") { + console.log("Root adaptor impl has already been deployed to: " + rootContracts.ROOT_ADAPTOR_IMPL_ADDRESS + ", skip."); + rootAdaptorImpl = getContract("RootAxelarBridgeAdaptor", rootContracts.ROOT_ADAPTOR_IMPL_ADDRESS, rootProvider); + } else { + console.log("Deploy root adaptor impl..."); + rootAdaptorImpl = await deployRootContract("RootAxelarBridgeAdaptor", rootDeployerWallet, null, rootGatewayAddr, deployerAddr); + console.log("Transaction submitted: ", JSON.stringify(rootAdaptorImpl.deployTransaction, null, 2)); + await waitForReceipt(rootAdaptorImpl.deployTransaction.hash, rootProvider); + rootContracts.ROOT_ADAPTOR_IMPL_ADDRESS = rootAdaptorImpl.address; + saveRootContracts(rootContracts); + console.log("Deployed to ROOT_ADAPTOR_IMPL_ADDRESS: ", rootAdaptorImpl.address); + await verifyRootContract("RootAxelarBridgeAdaptor", rootAdaptorImpl.address, `"constructor(address,address)" "${rootGatewayAddr}" "${deployerAddr}"`); + } + + if (rootDeployerWallet instanceof LedgerSigner) { + rootDeployerWallet.close(); + } + + // Get reserved wallet + let reservedDeployerWallet; + if (nonceReservedDeployerSecret == "ledger") { + let index = requireEnv("NONCE_RESERVED_DEPLOYER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + reservedDeployerWallet = new LedgerSigner(rootProvider, derivationPath); + } else { + reservedDeployerWallet = new ethers.Wallet(nonceReservedDeployerSecret, rootProvider); + } + let reservedDeployerAddr = await reservedDeployerWallet.getAddress(); + console.log("Reserved deployer address is: ", reservedDeployerAddr); + + // Deploy root token template + let rootTokenTemplate; + if (rootContracts.ROOT_TOKEN_TEMPLATE != "") { + console.log("Root token template has already been deployed to: " + rootContracts.ROOT_TOKEN_TEMPLATE + ", skip."); + rootTokenTemplate = getContract("ChildERC20", rootContracts.ROOT_TOKEN_TEMPLATE, rootProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await rootProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved != currentNonce) { + throw("Nonce mismatch, expected " + nonceReserved + " actual " + currentNonce); + } + console.log("Deploy root token template..."); + rootTokenTemplate = await deployRootContract("ChildERC20", reservedDeployerWallet, nonceReserved); + console.log("Transaction submitted: ", JSON.stringify(rootTokenTemplate.deployTransaction, null, 2)); + await waitForReceipt(rootTokenTemplate.deployTransaction.hash, rootProvider); + rootContracts.ROOT_TOKEN_TEMPLATE = rootTokenTemplate.address; + saveRootContracts(rootContracts); + console.log("Deployed to ROOT_TOKEN_TEMPLATE: ", rootTokenTemplate.address); + await verifyRootContract("ChildERC20", rootTokenTemplate.address, null); + } + + // Initialise template + if (await rootTokenTemplate.name() == "TEMPLATE") { + console.log("Root token template has already been initialised, skip."); + } else { + console.log("Initialise root token template..."); + let resp = await rootTokenTemplate.connect(reservedDeployerWallet).initialize("000000000000000000000000000000000000007B", "TEMPLATE", "TPT", 18); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + console.log("Initialized ROOT_TOKEN_TEMPLATE at: ", rootTokenTemplate.address); + + // Deploy proxy admin + let proxyAdmin; + if (rootContracts.ROOT_PROXY_ADMIN != "") { + console.log("Proxy admin has already been deployed to: " + rootContracts.ROOT_PROXY_ADMIN + ", skip."); + proxyAdmin = getContract("ProxyAdmin", rootContracts.ROOT_PROXY_ADMIN, rootProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await rootProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved + 2 != currentNonce) { + throw("Nonce mismatch, expected " + (nonceReserved + 2) + " actual " + currentNonce); + } + console.log("Deploy proxy admin..."); + proxyAdmin = await deployRootContract("ProxyAdmin", reservedDeployerWallet, null); + console.log("Transaction submitted: ", JSON.stringify(proxyAdmin.deployTransaction, null, 2)); + await waitForReceipt(proxyAdmin.deployTransaction.hash, rootProvider); + rootContracts.ROOT_PROXY_ADMIN = proxyAdmin.address; + saveRootContracts(rootContracts); + console.log("Deployed to ROOT_PROXY_ADMIN: ", proxyAdmin.address); + await verifyRootContract("ProxyAdmin", proxyAdmin.address, null); + } + + // Deploy root bridge proxy + let rootBridgeProxy; + if (rootContracts.ROOT_BRIDGE_PROXY_ADDRESS != "") { + console.log("Root bridge proxy has already been deployed to: " + rootContracts.ROOT_BRIDGE_PROXY_ADDRESS + ", skip."); + rootBridgeProxy = getContract("TransparentUpgradeableProxy", rootContracts.ROOT_BRIDGE_PROXY_ADDRESS, rootProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await rootProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved + 3 != currentNonce) { + throw("Nonce mismatch, expected " + (nonceReserved + 3) + " actual " + currentNonce); + } + console.log("Deploy root bridge proxy..."); + rootBridgeProxy = await deployRootContract("TransparentUpgradeableProxy", reservedDeployerWallet, null, rootBridgeImpl.address, proxyAdmin.address, []); + console.log("Transaction submitted: ", JSON.stringify(rootBridgeProxy.deployTransaction, null, 2)); + await waitForReceipt(rootBridgeProxy.deployTransaction.hash, rootProvider); + rootContracts.ROOT_BRIDGE_PROXY_ADDRESS = rootBridgeProxy.address; + saveRootContracts(rootContracts); + console.log("Deployed to ROOT_BRIDGE_PROXY_ADDRESS: ", rootBridgeProxy.address); + await verifyRootContract("TransparentUpgradeableProxy", rootBridgeProxy.address, `"constructor(address,address,bytes)" "${rootBridgeImpl.address}" "${proxyAdmin.address}" ""`); + } + + // Deploy root adaptor proxy + let rootAdaptorProxy; + if (rootContracts.ROOT_ADAPTOR_PROXY_ADDRESS != "") { + console.log("Root adaptor proxy has already been deployed to: " + rootContracts.ROOT_ADAPTOR_PROXY_ADDRESS + ", skip."); + rootAdaptorProxy = getContract("TransparentUpgradeableProxy", rootContracts.ROOT_ADAPTOR_PROXY_ADDRESS, rootProvider); + } else { + // Check the current nonce matches the reserved nonce + let currentNonce = await rootProvider.getTransactionCount(reservedDeployerAddr); + if (nonceReserved + 4 != currentNonce) { + throw("Nonce mismatch, expected " + (nonceReserved + 4) + " actual " + currentNonce); + } + console.log("Deploy root adaptor proxy..."); + rootAdaptorProxy = await deployRootContract("TransparentUpgradeableProxy", reservedDeployerWallet, null, rootAdaptorImpl.address, proxyAdmin.address, []); + console.log("Transaction submitted: ", JSON.stringify(rootAdaptorProxy.deployTransaction, null, 2)); + await waitForReceipt(rootAdaptorProxy.deployTransaction.hash, rootProvider); + rootContracts.ROOT_ADAPTOR_PROXY_ADDRESS = rootAdaptorProxy.address; + saveRootContracts(rootContracts); + console.log("Deployed to ROOT_ADAPTOR_PROXY_ADDRESS: ", rootAdaptorProxy.address); + await verifyRootContract("TransparentUpgradeableProxy", rootAdaptorProxy.address, `"constructor(address,address,bytes)" "${rootAdaptorImpl.address}" "${proxyAdmin.address}" ""`); + } + + rootContracts.ROOT_BRIDGE_ADDRESS = rootBridgeProxy.address; + rootContracts.ROOT_ADAPTOR_ADDRESS = rootAdaptorProxy.address; + saveRootContracts(rootContracts); +} \ No newline at end of file diff --git a/scripts/deploy/root_initialisation.js b/scripts/deploy/root_initialisation.js deleted file mode 100644 index bf49cc6d..00000000 --- a/scripts/deploy/root_initialisation.js +++ /dev/null @@ -1,195 +0,0 @@ -// Initialise root contracts -'use strict'; -require('dotenv').config(); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const { LedgerSigner } = require('@ethersproject/hardware-wallets') -const fs = require('fs'); - -exports.initialiseRootContracts = async() => { - // Check environment variables - let childChainName = helper.requireEnv("CHILD_CHAIN_NAME"); - let rootRPCURL = helper.requireEnv("ROOT_RPC_URL"); - let rootChainID = helper.requireEnv("ROOT_CHAIN_ID"); - let rootDeployerSecret = helper.requireEnv("ROOT_DEPLOYER_SECRET"); - let rootRateAdminSecret = helper.requireEnv("ROOT_BRIDGE_RATE_ADMIN_SECRET"); - let rootBridgeDefaultAdmin = helper.requireEnv("ROOT_BRIDGE_DEFAULT_ADMIN"); - let rootBridgePauser = helper.requireEnv("ROOT_BRIDGE_PAUSER"); - let rootBridgeUnpauser = helper.requireEnv("ROOT_BRIDGE_UNPAUSER"); - let rootBridgeVariableManager = helper.requireEnv("ROOT_BRIDGE_VARIABLE_MANAGER"); - let rootBridgeAdaptorManager = helper.requireEnv("ROOT_BRIDGE_ADAPTOR_MANAGER"); - let rootAdaptorDefaultAdmin = helper.requireEnv("ROOT_ADAPTOR_DEFAULT_ADMIN"); - let rootAdaptorBridgeManager = helper.requireEnv("ROOT_ADAPTOR_BRIDGE_MANAGER"); - let rootAdaptorGasServiceManager = helper.requireEnv("ROOT_ADAPTOR_GAS_SERVICE_MANAGER"); - let rootAdaptorTargetManager = helper.requireEnv("ROOT_ADAPTOR_TARGET_MANAGER"); - let rootGasServiceAddr = helper.requireEnv("ROOT_GAS_SERVICE_ADDRESS"); - let rootIMXAddr = helper.requireEnv("ROOT_IMX_ADDR"); - let rootWETHAddr = helper.requireEnv("ROOT_WETH_ADDR"); - let imxDepositLimit = helper.requireEnv("IMX_DEPOSIT_LIMIT"); - let rateLimitIMXCap = helper.requireEnv("RATE_LIMIT_IMX_CAPACITY"); - let rateLimitIMXRefill = helper.requireEnv("RATE_LIMIT_IMX_REFILL_RATE"); - let rateLimitIMXLargeThreshold = helper.requireEnv("RATE_LIMIT_IMX_LARGE_THRESHOLD"); - let rateLimitETHCap = helper.requireEnv("RATE_LIMIT_ETH_CAPACITY"); - let rateLimitETHRefill = helper.requireEnv("RATE_LIMIT_ETH_REFILL_RATE"); - let rateLimitETHLargeThreshold = helper.requireEnv("RATE_LIMIT_ETH_LARGE_THRESHOLD"); - let rateLimitUSDCAddr = helper.requireEnv("RATE_LIMIT_USDC_ADDR"); - let rateLimitUSDCCap = helper.requireEnv("RATE_LIMIT_USDC_CAPACITY"); - let rateLimitUSDCRefill = helper.requireEnv("RATE_LIMIT_USDC_REFILL_RATE"); - let rateLimitUSDCLargeThreshold = helper.requireEnv("RATE_LIMIT_USDC_LARGE_THRESHOLD"); - let rateLimitGUAddr = helper.requireEnv("RATE_LIMIT_GU_ADDR"); - let rateLimitGUCap = helper.requireEnv("RATE_LIMIT_GU_CAPACITY"); - let rateLimitGURefill = helper.requireEnv("RATE_LIMIT_GU_REFILL_RATE"); - let rateLimitGULargeThreshold = helper.requireEnv("RATE_LIMIT_GU_LARGE_THRESHOLD"); - let rateLimitCheckMateAddr = helper.requireEnv("RATE_LIMIT_CHECKMATE_ADDR"); - let rateLimitCheckMateCap = helper.requireEnv("RATE_LIMIT_CHECKMATE_CAPACITY"); - let rateLimitCheckMateRefill = helper.requireEnv("RATE_LIMIT_CHECKMATE_REFILL_RATE"); - let rateLimitCheckMateLargeThreshold = helper.requireEnv("RATE_LIMIT_CHECKMATE_LARGE_THRESHOLD"); - let rateLimitGOGAddr = helper.requireEnv("RATE_LIMIT_GOG_ADDR"); - let rateLimitGOGCap = helper.requireEnv("RATE_LIMIT_GOG_CAPACITY"); - let rateLimitGOGRefill = helper.requireEnv("RATE_LIMIT_GOG_REFILL_RATE"); - let rateLimitGOGLargeThreshold = helper.requireEnv("RATE_LIMIT_GOG_LARGE_THRESHOLD"); - - // Read from contract file. - let data = fs.readFileSync(".child.bridge.contracts.json", 'utf-8'); - let childContracts = JSON.parse(data); - let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; - let childAdaptorAddr = childContracts.CHILD_ADAPTOR_ADDRESS; - data = fs.readFileSync(".root.bridge.contracts.json", 'utf-8'); - let rootContracts = JSON.parse(data); - let rootBridgeAddr = rootContracts.ROOT_BRIDGE_ADDRESS; - let rootAdaptorAddr = rootContracts.ROOT_ADAPTOR_ADDRESS; - let rootTemplateAddr = rootContracts.ROOT_TOKEN_TEMPLATE; - - // Get admin address - const rootProvider = new ethers.providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); - let adminWallet; - if (rootDeployerSecret == "ledger") { - adminWallet = new LedgerSigner(rootProvider); - } else { - adminWallet = new ethers.Wallet(rootDeployerSecret, rootProvider); - } - let adminAddr = await adminWallet.getAddress(); - console.log("Deployer address is: ", adminAddr); - - // Get rate admin address - let rateAdminWallet; - if (rootRateAdminSecret == "ledger") { - rateAdminWallet = new LedgerSigner(rateAdminWallet); - } else { - rateAdminWallet = new ethers.Wallet(rootRateAdminSecret, rootProvider); - } - let rateAdminAddr = await rateAdminWallet.getAddress(); - console.log("Rate admin address is: ", rateAdminAddr); - - - // Execute - console.log("Initialise root contracts in..."); - await helper.waitForConfirmation(); - - // Initialise root bridge - let rootBridgeObj = JSON.parse(fs.readFileSync('../../out/RootERC20BridgeFlowRate.sol/RootERC20BridgeFlowRate.json', 'utf8')); - console.log("Initialise root bridge..."); - let rootBridge = new ethers.Contract(rootBridgeAddr, rootBridgeObj.abi, rootProvider); - let resp = await rootBridge.connect(adminWallet)["initialize((address,address,address,address,address),address,address,address,address,address,uint256,address)"]( - { - defaultAdmin: rootBridgeDefaultAdmin, - pauser: rootBridgePauser, - unpauser: rootBridgeUnpauser, - variableManager: rootBridgeVariableManager, - adaptorManager: rootBridgeAdaptorManager, - }, - rootAdaptorAddr, - childBridgeAddr, - rootTemplateAddr, - rootIMXAddr, - rootWETHAddr, - ethers.utils.parseEther(imxDepositLimit), - rateAdminAddr); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - - // Configure rate - // IMX - console.log("Configure rate limiting for IMX...") - resp = await rootBridge.connect(rateAdminWallet).setRateControlThreshold( - rootIMXAddr, - ethers.utils.parseEther(rateLimitIMXCap), - ethers.utils.parseEther(rateLimitIMXRefill), - ethers.utils.parseEther(rateLimitIMXLargeThreshold) - ); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - - // ETH - console.log("Configure rate limiting for ETH...") - resp = await rootBridge.connect(rateAdminWallet).setRateControlThreshold( - await rootBridge.NATIVE_ETH(), - ethers.utils.parseEther(rateLimitETHCap), - ethers.utils.parseEther(rateLimitETHRefill), - ethers.utils.parseEther(rateLimitETHLargeThreshold) - ); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - - // USDC - console.log("Configure rate limiting for USDC...") - resp = await rootBridge.connect(rateAdminWallet).setRateControlThreshold( - rateLimitUSDCAddr, - ethers.utils.parseEther(rateLimitUSDCCap), - ethers.utils.parseEther(rateLimitUSDCRefill), - ethers.utils.parseEther(rateLimitUSDCLargeThreshold) - ); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - - // GU - console.log("Configure rate limiting for GU...") - resp = await rootBridge.connect(rateAdminWallet).setRateControlThreshold( - rateLimitGUAddr, - ethers.utils.parseEther(rateLimitGUCap), - ethers.utils.parseEther(rateLimitGURefill), - ethers.utils.parseEther(rateLimitGULargeThreshold) - ); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - - // Checkmate - console.log("Configure rate limiting for CheckMate...") - resp = await rootBridge.connect(rateAdminWallet).setRateControlThreshold( - rateLimitCheckMateAddr, - ethers.utils.parseEther(rateLimitCheckMateCap), - ethers.utils.parseEther(rateLimitCheckMateRefill), - ethers.utils.parseEther(rateLimitCheckMateLargeThreshold) - ); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - - // GOG - console.log("Configure rate limiting for GOG...") - resp = await rootBridge.connect(rateAdminWallet).setRateControlThreshold( - rateLimitGOGAddr, - ethers.utils.parseEther(rateLimitGOGCap), - ethers.utils.parseEther(rateLimitGOGRefill), - ethers.utils.parseEther(rateLimitGOGLargeThreshold) - ); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); - - // Initialise root adaptor - let rootAdaptorObj = JSON.parse(fs.readFileSync('../../out/RootAxelarBridgeAdaptor.sol/RootAxelarBridgeAdaptor.json', 'utf8')); - console.log("Initialise root adaptor..."); - let rootAdaptor = new ethers.Contract(rootAdaptorAddr, rootAdaptorObj.abi, rootProvider); - resp = await rootAdaptor.connect(adminWallet).initialize( - { - defaultAdmin: rootAdaptorDefaultAdmin, - bridgeManager: rootAdaptorBridgeManager, - gasServiceManager: rootAdaptorGasServiceManager, - targetManager: rootAdaptorTargetManager, - }, - rootBridgeAddr, - childChainName, - childAdaptorAddr, - rootGasServiceAddr); - console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); - await helper.waitForReceipt(resp.hash, rootProvider); -} \ No newline at end of file diff --git a/scripts/deploy/root_initialisation.ts b/scripts/deploy/root_initialisation.ts new file mode 100644 index 00000000..4f826bf5 --- /dev/null +++ b/scripts/deploy/root_initialisation.ts @@ -0,0 +1,231 @@ +// Initialise root contracts +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers, utils } from "ethers"; +import { requireEnv, waitForConfirmation, waitForReceipt, getContract, getChildContracts, getRootContracts } from "../helpers/helpers"; +import { LedgerSigner } from "../helpers/ledger_signer"; +import { RetryProvider } from "../helpers/retry"; + +export async function initialiseRootContracts() { + // Check environment variables + let childChainName = requireEnv("CHILD_CHAIN_NAME"); + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let deployerSecret = requireEnv("DEPLOYER_SECRET"); + let rootGasServiceAddr = requireEnv("ROOT_GAS_SERVICE_ADDRESS"); + let rootIMXAddr = requireEnv("ROOT_IMX_ADDR"); + let rootWETHAddr = requireEnv("ROOT_WETH_ADDR"); + let imxDepositLimit = requireEnv("IMX_DEPOSIT_LIMIT"); + let rootPrivilegedMultisig = requireEnv("ROOT_PRIVILEGED_MULTISIG_ADDR"); + let rootBreakglass = requireEnv("ROOT_BREAKGLASS_ADDR"); + let rateLimitIMXCap = requireEnv("RATE_LIMIT_IMX_CAPACITY"); + let rateLimitIMXRefill = requireEnv("RATE_LIMIT_IMX_REFILL_RATE"); + let rateLimitIMXLargeThreshold = requireEnv("RATE_LIMIT_IMX_LARGE_THRESHOLD"); + let rateLimitETHCap = requireEnv("RATE_LIMIT_ETH_CAPACITY"); + let rateLimitETHRefill = requireEnv("RATE_LIMIT_ETH_REFILL_RATE"); + let rateLimitETHLargeThreshold = requireEnv("RATE_LIMIT_ETH_LARGE_THRESHOLD"); + let rateLimitUSDCAddr = requireEnv("RATE_LIMIT_USDC_ADDR"); + let rateLimitUSDCCap = requireEnv("RATE_LIMIT_USDC_CAPACITY"); + let rateLimitUSDCRefill = requireEnv("RATE_LIMIT_USDC_REFILL_RATE"); + let rateLimitUSDCLargeThreshold = requireEnv("RATE_LIMIT_USDC_LARGE_THRESHOLD"); + // let rateLimitGUAddr = requireEnv("RATE_LIMIT_GU_ADDR"); + // let rateLimitGUCap = requireEnv("RATE_LIMIT_GU_CAPACITY"); + // let rateLimitGURefill = requireEnv("RATE_LIMIT_GU_REFILL_RATE"); + // let rateLimitGULargeThreshold = requireEnv("RATE_LIMIT_GU_LARGE_THRESHOLD"); + // let rateLimitCheckMateAddr = requireEnv("RATE_LIMIT_CHECKMATE_ADDR"); + // let rateLimitCheckMateCap = requireEnv("RATE_LIMIT_CHECKMATE_CAPACITY"); + // let rateLimitCheckMateRefill = requireEnv("RATE_LIMIT_CHECKMATE_REFILL_RATE"); + // let rateLimitCheckMateLargeThreshold = requireEnv("RATE_LIMIT_CHECKMATE_LARGE_THRESHOLD"); + // let rateLimitGOGAddr = requireEnv("RATE_LIMIT_GOG_ADDR"); + // let rateLimitGOGCap = requireEnv("RATE_LIMIT_GOG_CAPACITY"); + // let rateLimitGOGRefill = requireEnv("RATE_LIMIT_GOG_REFILL_RATE"); + // let rateLimitGOGLargeThreshold = requireEnv("RATE_LIMIT_GOG_LARGE_THRESHOLD"); + + // Read from contract file. + let childContracts = getChildContracts(); + let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; + let childAdaptorAddr = childContracts.CHILD_ADAPTOR_ADDRESS; + let rootContracts = getRootContracts(); + let rootBridgeAddr = rootContracts.ROOT_BRIDGE_ADDRESS; + let rootAdaptorAddr = rootContracts.ROOT_ADAPTOR_ADDRESS; + let rootTemplateAddr = rootContracts.ROOT_TOKEN_TEMPLATE; + + // Get deployer address + const rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); + let rootDeployerWallet; + if (deployerSecret == "ledger") { + let index = requireEnv("DEPLOYER_LEDGER_INDEX"); + const derivationPath = `m/44'/60'/${parseInt(index)}'/0/0`; + rootDeployerWallet = new LedgerSigner(rootProvider, derivationPath); + } else { + rootDeployerWallet = new ethers.Wallet(deployerSecret, rootProvider); + } + let deployerAddr = await rootDeployerWallet.getAddress(); + console.log("Deployer address is: ", deployerAddr); + + // Execute + console.log("Initialise root contracts in..."); + await waitForConfirmation(); + + // Initialise root bridge + let rootBridge = getContract("RootERC20BridgeFlowRate", rootBridgeAddr, rootProvider); + if (await rootBridge.rootIMXToken() != "0x0000000000000000000000000000000000000000") { + console.log("Root bridge has already been initialised, skip."); + } else { + console.log("Initialise root bridge..."); + let resp = await rootBridge.connect(rootDeployerWallet)["initialize((address,address,address,address,address),address,address,address,address,address,uint256,address)"]( + { + defaultAdmin: deployerAddr, + pauser: rootBreakglass, + unpauser: rootPrivilegedMultisig, + variableManager: rootPrivilegedMultisig, + adaptorManager: rootPrivilegedMultisig, + }, + rootAdaptorAddr, + childBridgeAddr, + rootTemplateAddr, + rootIMXAddr, + rootWETHAddr, + ethers.utils.parseEther(imxDepositLimit), + deployerAddr); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + // Configure rate + // IMX + if ((await rootBridge.largeTransferThresholds(rootIMXAddr)).toString() != "0") { + console.log("IMX rate limiting has already been configured, skip."); + } else { + console.log("Configure rate limiting for IMX...") + let resp = await rootBridge.connect(rootDeployerWallet).setRateControlThreshold( + rootIMXAddr, + ethers.utils.parseEther(rateLimitIMXCap), + ethers.utils.parseEther(rateLimitIMXRefill), + ethers.utils.parseEther(rateLimitIMXLargeThreshold) + ); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + // ETH + if ((await rootBridge.largeTransferThresholds(await rootBridge.NATIVE_ETH())).toString() != "0") { + console.log("ETH rate limiting has already been configured, skip."); + } else { + console.log("Configure rate limiting for ETH...") + let resp = await rootBridge.connect(rootDeployerWallet).setRateControlThreshold( + await rootBridge.NATIVE_ETH(), + ethers.utils.parseEther(rateLimitETHCap), + ethers.utils.parseEther(rateLimitETHRefill), + ethers.utils.parseEther(rateLimitETHLargeThreshold) + ); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + // USDC + if ((await rootBridge.largeTransferThresholds(rateLimitUSDCAddr)).toString() != "0") { + console.log("USDC rate limiting has already been configured, skip."); + } else { + console.log("Configure rate limiting for USDC...") + let resp = await rootBridge.connect(rootDeployerWallet).setRateControlThreshold( + rateLimitUSDCAddr, + ethers.utils.parseUnits(rateLimitUSDCCap, 6), + ethers.utils.parseUnits(rateLimitUSDCRefill, 6), + ethers.utils.parseUnits(rateLimitUSDCLargeThreshold, 6) + ); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + // // GU + // if ((await rootBridge.largeTransferThresholds(rateLimitGUAddr)).toString() != "0") { + // console.log("GU rate limiting has already been configured, skip."); + // } else { + // console.log("Configure rate limiting for GU...") + // let resp = await rootBridge.connect(rootDeployerWallet).setRateControlThreshold( + // rateLimitGUAddr, + // ethers.utils.parseEther(rateLimitGUCap), + // ethers.utils.parseEther(rateLimitGURefill), + // ethers.utils.parseEther(rateLimitGULargeThreshold) + // ); + // console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + // await waitForReceipt(resp.hash, rootProvider); + // } + + // // Checkmate + // if ((await rootBridge.largeTransferThresholds(rateLimitCheckMateAddr)).toString() != "0") { + // console.log("CheckMate rate limiting has already been configured, skip."); + // } else { + // console.log("Configure rate limiting for CheckMate...") + // let resp = await rootBridge.connect(rootDeployerWallet).setRateControlThreshold( + // rateLimitCheckMateAddr, + // ethers.utils.parseEther(rateLimitCheckMateCap), + // ethers.utils.parseEther(rateLimitCheckMateRefill), + // ethers.utils.parseEther(rateLimitCheckMateLargeThreshold) + // ); + // console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + // await waitForReceipt(resp.hash, rootProvider); + // } + + // // GOG + // if ((await rootBridge.largeTransferThresholds(rateLimitGOGAddr)).toString() != "0") { + // console.log("GOG rate limiting has already been configured, skip."); + // } else { + // console.log("Configure rate limiting for GOG...") + // let resp = await rootBridge.connect(rootDeployerWallet).setRateControlThreshold( + // rateLimitGOGAddr, + // ethers.utils.parseEther(rateLimitGOGCap), + // ethers.utils.parseEther(rateLimitGOGRefill), + // ethers.utils.parseEther(rateLimitGOGLargeThreshold) + // ); + // console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + // await waitForReceipt(resp.hash, rootProvider); + // } + + // Grant roles + if (await rootBridge.hasRole(utils.keccak256(utils.toUtf8Bytes("RATE")), rootPrivilegedMultisig)) { + console.log("Multisig has already obtained RATE_CONTROL_ROLE..., skip."); + } else { + console.log("Grant RATE_CONTROL_ROLE to multisig..."); + let resp = await rootBridge.connect(rootDeployerWallet).grantRole(utils.keccak256(utils.toUtf8Bytes("RATE")), rootPrivilegedMultisig); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + if (await rootBridge.hasRole(await rootBridge.DEFAULT_ADMIN_ROLE(), rootPrivilegedMultisig)) { + console.log("Multisig has already obtained DEFAULT_ADMIN..., skip."); + } else { + console.log("Grant DEFAULT_ADMIN to multisig...") + let resp = await rootBridge.connect(rootDeployerWallet).grantRole(await rootBridge.DEFAULT_ADMIN_ROLE(), rootPrivilegedMultisig); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } + + // Print summary + console.log("Does multisig have DEFAULT_ADMIN: ", await rootBridge.hasRole(await rootBridge.DEFAULT_ADMIN_ROLE(), rootPrivilegedMultisig)); + console.log("Does deployer have DEFAULT_ADMIN: ", await rootBridge.hasRole(await rootBridge.DEFAULT_ADMIN_ROLE(), deployerAddr)); + console.log("Does multisig have RATE_ADMIN: ", await rootBridge.hasRole(utils.keccak256(utils.toUtf8Bytes("RATE")), rootPrivilegedMultisig)); + console.log("Does deployer have RATE_ADMIN: ", await rootBridge.hasRole(utils.keccak256(utils.toUtf8Bytes("RATE")), deployerAddr)); + + // Initialise root adaptor + let rootAdaptor = getContract("RootAxelarBridgeAdaptor", rootAdaptorAddr, rootProvider); + if (await rootAdaptor.gasService() != "0x0000000000000000000000000000000000000000") { + console.log("Root adaptor has already been initialized, skip."); + } else { + console.log("Initialise root adaptor..."); + let resp = await rootAdaptor.connect(rootDeployerWallet).initialize( + { + defaultAdmin: rootPrivilegedMultisig, + bridgeManager: rootPrivilegedMultisig, + gasServiceManager: rootPrivilegedMultisig, + targetManager: rootPrivilegedMultisig, + }, + rootBridgeAddr, + childChainName, + childAdaptorAddr, + rootGasServiceAddr); + console.log("Transaction submitted: ", JSON.stringify(resp, null, 2)); + await waitForReceipt(resp.hash, rootProvider); + } +} \ No newline at end of file diff --git a/scripts/e2e/.root.bridge.contracts.json.example b/scripts/e2e/.root.bridge.contracts.json.example index 4a593209..8484c978 100644 --- a/scripts/e2e/.root.bridge.contracts.json.example +++ b/scripts/e2e/.root.bridge.contracts.json.example @@ -1,5 +1,6 @@ { "ROOT_BRIDGE_ADDRESS": "", "ROOT_ADAPTOR_ADDRESS": "", - "ROOT_TOKEN_TEMPLATE": "" + "ROOT_TOKEN_TEMPLATE": "", + "ROOT_TEST_CUSTOM_TOKEN": "" } \ No newline at end of file diff --git a/scripts/e2e/README.md b/scripts/e2e/README.md index 8fb2cd88..914ea035 100644 --- a/scripts/e2e/README.md +++ b/scripts/e2e/README.md @@ -45,11 +45,12 @@ TEST_ACCOUNT_SECRET= { "ROOT_BRIDGE_ADDRESS": "", "ROOT_ADAPTOR_ADDRESS": "", - "ROOT_TOKEN_TEMPLATE": "" + "ROOT_TOKEN_TEMPLATE": "", + "ROOT_TEST_CUSTOM_TOKEN": "" } ``` 3. Run end to end tests ``` -npx mocha --require mocha-suppress-logs . +AXELAR_API_URL=${Axelar API URL or "skip" if run on local} npx mocha --require mocha-suppress-logs ./e2e.ts ``` \ No newline at end of file diff --git a/scripts/e2e/e2e.js b/scripts/e2e/e2e.js deleted file mode 100644 index 964432e2..00000000 --- a/scripts/e2e/e2e.js +++ /dev/null @@ -1,392 +0,0 @@ -// End to end tests -'use strict'; -require('dotenv').config(); -const { ethers, ContractFactory } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const fs = require('fs'); -const { expect } = require("chai"); - -// The contract ABI of IMX on L1. -const IMX_ABI = `[{"inputs":[{"internalType":"address","name":"minter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]`; - -describe("Bridge e2e test", () => { - let rootProvider; - let childProvider; - let rootTestWallet; - let childTestWallet; - let rootBridge; - let rootWETH; - let rootIMX; - let childBridge; - let childETH; - let childWIMX; - let rootCustomToken; - let childCustomToken; - - before(async function () { - this.timeout(30000); - let rootRPCURL = helper.requireEnv("ROOT_RPC_URL"); - let rootChainID = helper.requireEnv("ROOT_CHAIN_ID"); - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let rootRateAdminSecret = helper.requireEnv("ROOT_BRIDGE_RATE_ADMIN_SECRET"); - let testAccountKey = helper.requireEnv("TEST_ACCOUNT_SECRET"); - let rootIMXAddr = helper.requireEnv("ROOT_IMX_ADDR"); - let rootWETHAddr = helper.requireEnv("ROOT_WETH_ADDR"); - - // Read from contract file. - let data = fs.readFileSync(".child.bridge.contracts.json", 'utf-8'); - let childContracts = JSON.parse(data); - let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; - let childWIMXAddr = childContracts.WRAPPED_IMX_ADDRESS; - data = fs.readFileSync(".root.bridge.contracts.json", 'utf-8'); - let rootContracts = JSON.parse(data); - let rootBridgeAddr = rootContracts.ROOT_BRIDGE_ADDRESS; - - rootProvider = new ethers.providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); - childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - rootTestWallet = new ethers.Wallet(testAccountKey, rootProvider); - childTestWallet = new ethers.Wallet(testAccountKey, childProvider); - let rootRateAdminWallet = new ethers.Wallet(rootRateAdminSecret, rootProvider); - - let rootBridgeObj = JSON.parse(fs.readFileSync('../../out/RootERC20BridgeFlowRate.sol/RootERC20BridgeFlowRate.json', 'utf8')); - rootBridge = new ethers.Contract(rootBridgeAddr, rootBridgeObj.abi, rootProvider); - - let WETHObj = JSON.parse(fs.readFileSync('../../out/WETH.sol/WETH.json', 'utf8')) - rootWETH = new ethers.Contract(rootWETHAddr, WETHObj.abi, rootProvider); - - rootIMX = new ethers.Contract(rootIMXAddr, IMX_ABI, rootProvider); - - let childBridgeObj = JSON.parse(fs.readFileSync('../../out/ChildERC20Bridge.sol/ChildERC20Bridge.json', 'utf8')); - childBridge = new ethers.Contract(childBridgeAddr, childBridgeObj.abi, childProvider); - - let childEthTokenAddr = await childBridge.childETHToken(); - let childTokenTemplateObj = JSON.parse(fs.readFileSync('../../out/ChildERC20.sol/ChildERC20.json', 'utf8')); - childETH = new ethers.Contract(childEthTokenAddr, childTokenTemplateObj.abi, childProvider); - - let wrappedIMXObj = JSON.parse(fs.readFileSync('../../out/WIMX.sol/WIMX.json', 'utf8')); - childWIMX = new ethers.Contract(childWIMXAddr, wrappedIMXObj.abi, childProvider); - - // Deploy a custom token - let customTokenObj = JSON.parse(fs.readFileSync('../../out/ERC20PresetMinterPauser.sol/ERC20PresetMinterPauser.json', 'utf8')); - let factory = new ContractFactory(customTokenObj.abi, customTokenObj.bytecode, rootTestWallet); - rootCustomToken = await factory.deploy("Custom Token", "CTK"); - await helper.waitForReceipt(rootCustomToken.deployTransaction.hash, rootProvider); - // Mint tokens - let resp = await rootCustomToken.connect(rootTestWallet).mint(rootTestWallet.address, ethers.utils.parseEther("1000.0").toBigInt()); - await helper.waitForReceipt(resp.hash, rootProvider); - // Set rate control - resp = await rootBridge.connect(rootRateAdminWallet).setRateControlThreshold( - rootCustomToken.address, - ethers.utils.parseEther("20016.0"), - ethers.utils.parseEther("5.56"), - ethers.utils.parseEther("10008.0") - ); - await helper.waitForReceipt(resp.hash, rootProvider); - }) - - it("should successfully deposit IMX to self from L1 to L2", async() => { - // Get IMX balance on root & child chains before deposit - let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); - let preBalL2 = await childProvider.getBalance(childTestWallet.address); - - let amt = ethers.utils.parseEther("10.0"); - let bridgeFee = ethers.utils.parseEther("0.001"); - - // Approve - let resp = await rootIMX.connect(rootTestWallet).approve(rootBridge.address, amt); - await helper.waitForReceipt(resp.hash, rootProvider); - - // IMX deposit L1 to L2 - resp = await rootBridge.connect(rootTestWallet).deposit(rootIMX.address, amt, { - value: bridgeFee, - }); - await helper.waitForReceipt(resp.hash, rootProvider); - - let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); - let postBalL2 = preBalL2; - - while (postBalL2.eq(preBalL2)) { - postBalL2 = await childProvider.getBalance(childTestWallet.address); - await helper.delay(1000); - } - - // Verify - let expectedPostL1 = preBalL1.sub(amt); - let expectedPostL2 = preBalL2.add(amt); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(60000) - - it("should successfully withdraw IMX to self from L2 to L1", async() => { - // Get IMX balance on root & child chains before withdraw - let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); - let preBalL2 = await childProvider.getBalance(childTestWallet.address); - - let amt = ethers.utils.parseEther("1.0"); - let bridgeFee = ethers.utils.parseEther("1.0"); - - // IMX withdraw L2 to L1 - let [priorityFee, maxFee] = await helper.getFee(childTestWallet); - let resp = await childBridge.connect(childTestWallet).withdrawIMX(amt, { - value: amt.add(bridgeFee), - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - await helper.waitForReceipt(resp.hash, childProvider); - - let postBalL1 = preBalL1; - let postBalL2 = await childProvider.getBalance(childTestWallet.address); - - while (postBalL1.eq(preBalL1)) { - postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); - await helper.delay(1000); - } - - // Verify - let receipt = await childProvider.getTransactionReceipt(resp.hash); - let txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice); - let expectedPostL1 = preBalL1.add(amt); - let expectedPostL2 = preBalL2.sub(txFee).sub(amt).sub(bridgeFee); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(120000) - - it("should successfully withdraw wIMX to self from L2 to L1", async() => { - // Wrap 1 IMX - let [priorityFee, maxFee] = await helper.getFee(childTestWallet); - let resp = await childWIMX.connect(childTestWallet).deposit({ - value: ethers.utils.parseEther("1.0"), - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - await helper.waitForReceipt(resp.hash, childProvider); - - // Get IMX balance on root & child chains before withdraw - let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); - let preBalL2 = await childWIMX.balanceOf(childTestWallet.address); - - let amt = ethers.utils.parseEther("0.5"); - let bridgeFee = ethers.utils.parseEther("1.0"); - - // Approve - [priorityFee, maxFee] = await helper.getFee(childTestWallet); - resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt, { - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - await helper.waitForReceipt(resp.hash, childProvider); - - // wIMX withdraw L2 to L1 - [priorityFee, maxFee] = await helper.getFee(childTestWallet); - resp = await childBridge.connect(childTestWallet).withdrawWIMX(amt, { - value: bridgeFee, - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - await helper.waitForReceipt(resp.hash, childProvider); - - let postBalL1 = preBalL1; - let postBalL2 = await childWIMX.balanceOf(childTestWallet.address); - - while (postBalL1.eq(preBalL1)) { - postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); - await helper.delay(1000); - } - - // Verify - let expectedPostL1 = preBalL1.add(amt); - let expectedPostL2 = preBalL2.sub(amt); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(120000) - - it("should successfully deposit ETH to self from L1 to L2", async() => { - // Get ETH balance on root & child chains before deposit - let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); - let preBalL2 = await childETH.balanceOf(childTestWallet.address); - - let amt = ethers.utils.parseEther("0.001"); - let bridgeFee = ethers.utils.parseEther("0.001"); - - // ETH deposit L1 to L2 - let resp = await rootBridge.connect(rootTestWallet).depositETH(amt, { - value: amt.add(bridgeFee), - }); - await helper.waitForReceipt(resp.hash, rootProvider); - - let postBalL1 = await rootProvider.getBalance(rootTestWallet.address); - let postBalL2 = preBalL2; - - while (postBalL2.eq(preBalL2)) { - postBalL2 = await childETH.balanceOf(childTestWallet.address); - await helper.delay(1000); - } - - // Verify - let receipt = await rootProvider.getTransactionReceipt(resp.hash); - let txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice); - let expectedPostL1 = preBalL1.sub(txFee).sub(amt).sub(bridgeFee); - let expectedPostL2 = preBalL2.add(amt); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(60000) - - it("should successfully deposit wETH to self from L1 to L2", async() => { - // Wrap 0.01 ETH - let resp = await rootWETH.connect(rootTestWallet).deposit({ - value: ethers.utils.parseEther("0.01"), - }) - await helper.waitForReceipt(resp.hash, rootProvider); - - // Get ETH balance on root & child chains before withdraw - let preBalL1 = await rootWETH.balanceOf(rootTestWallet.address); - let preBalL2 = await childETH.balanceOf(childTestWallet.address); - - let amt = ethers.utils.parseEther("0.001"); - let bridgeFee = ethers.utils.parseEther("0.001"); - - // Approve - resp = await rootWETH.connect(rootTestWallet).approve(rootBridge.address, amt); - await helper.waitForReceipt(resp.hash, rootProvider); - - // wETH deposit L1 to L2 - resp = await rootBridge.connect(rootTestWallet).deposit(rootWETH.address, amt, { - value: bridgeFee, - }) - await helper.waitForReceipt(resp.hash, rootProvider); - - let postBalL1 = await rootWETH.balanceOf(rootTestWallet.address); - let postBalL2 = preBalL2; - - while (postBalL2.eq(preBalL2)) { - postBalL2 = await childETH.balanceOf(childTestWallet.address); - await helper.delay(1000); - } - - // Verify - let expectedPostL1 = preBalL1.sub(amt); - let expectedPostL2 = preBalL2.add(amt); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(60000) - - it("should successfully withdraw ETH to self from L2 to L1", async() => { - // Get ETH balance on root & child chains before withdraw - let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); - let preBalL2 = await childETH.balanceOf(childTestWallet.address); - - let amt = ethers.utils.parseEther("0.0005"); - let bridgeFee = ethers.utils.parseEther("1.0"); - - // ETH withdraw L2 to L1 - let [priorityFee, maxFee] = await helper.getFee(childTestWallet); - let resp = await childBridge.connect(childTestWallet).withdrawETH(amt, { - value: bridgeFee, - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); - await helper.waitForReceipt(resp.hash, childProvider); - - let postBalL1 = preBalL1; - let postBalL2 = await childETH.balanceOf(childTestWallet.address); - - while (postBalL1.eq(preBalL1)) { - postBalL1 = await rootProvider.getBalance(rootTestWallet.address); - await helper.delay(1000); - } - - // Verify - let expectedPostL1 = preBalL1.add(amt); - let expectedPostL2 = preBalL2.sub(amt); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(120000) - - it("should successfully map a ERC20 Token", async() => { - // Map token - let bridgeFee = ethers.utils.parseEther("0.001"); - let expectedChildTokenAddr = await rootBridge.callStatic.mapToken(rootCustomToken.address, { - value: bridgeFee, - }); - let resp = await rootBridge.connect(rootTestWallet).mapToken(rootCustomToken.address, { - value: bridgeFee, - }) - await helper.waitForReceipt(resp.hash, rootProvider); - - let childTokenAddr = await childBridge.rootTokenToChildToken(rootCustomToken.address); - while (childTokenAddr == ethers.constants.AddressZero) { - childTokenAddr = await childBridge.rootTokenToChildToken(rootCustomToken.address); - await helper.delay(1000); - } - let childTokenTemplateObj = JSON.parse(fs.readFileSync('../../out/ChildERC20.sol/ChildERC20.json', 'utf8')); - childCustomToken = new ethers.Contract(childTokenAddr, childTokenTemplateObj.abi, childProvider); - - // Verify - expect(childTokenAddr).to.equal(expectedChildTokenAddr); - }).timeout(60000) - - it("should successfully deposit mapped ERC20 Token to self from L1 to L2", async() => { - // Get token balance on root & child chains before deposit - let preBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); - let preBalL2 = await childCustomToken.balanceOf(childTestWallet.address); - - let amt = ethers.utils.parseEther("1.0"); - let bridgeFee = ethers.utils.parseEther("0.001"); - - // Approve - let resp = await rootCustomToken.connect(rootTestWallet).approve(rootBridge.address, amt); - await helper.waitForReceipt(resp.hash, rootProvider); - - // Token deposit L1 to L2 - resp = await rootBridge.connect(rootTestWallet).deposit(rootCustomToken.address, amt, { - value: bridgeFee, - }) - await helper.waitForReceipt(resp.hash, rootProvider); - - let postBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); - let postBalL2 = preBalL2; - while (postBalL2.eq(preBalL2)) { - postBalL2 = await childCustomToken.balanceOf(childTestWallet.address); - await helper.delay(1000); - } - - // Verify - let expectedPostL1 = preBalL1.sub(amt); - let expectedPostL2 = preBalL2.add(amt); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(60000) - - it("should successfully withdraw mapped ERC20 Token to self from L2 to L1", async() => { - // Get token balance on root & child chains before deposit - let preBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); - let preBalL2 = await childCustomToken.balanceOf(childTestWallet.address); - - let amt = ethers.utils.parseEther("0.5"); - let bridgeFee = ethers.utils.parseEther("1.0"); - - // Token withdraw L2 to L1 - let [priorityFee, maxFee] = await helper.getFee(childTestWallet); - let resp = await childBridge.connect(childTestWallet).withdraw(childCustomToken.address, amt, { - value: bridgeFee, - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }) - await helper.waitForReceipt(resp.hash, childProvider); - - let postBalL1 = preBalL1; - let postBalL2 = await childCustomToken.balanceOf(childTestWallet.address); - - while (postBalL1.eq(preBalL1)) { - postBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); - await helper.delay(1000); - } - - // Verify - let expectedPostL1 = preBalL1.add(amt); - let expectedPostL2 = preBalL2.sub(amt); - expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); - expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); - }).timeout(120000) -}) \ No newline at end of file diff --git a/scripts/e2e/e2e.ts b/scripts/e2e/e2e.ts new file mode 100644 index 00000000..7998941d --- /dev/null +++ b/scripts/e2e/e2e.ts @@ -0,0 +1,1900 @@ +// End to end tests +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers, providers } from "ethers"; +import { requireEnv, waitForReceipt, getFee, getContract, delay, getChildContracts, getRootContracts, saveChildContracts, waitUntilSucceed } from "../helpers/helpers"; +import * as chai from "chai"; +chai.use(require('chai-as-promised')); +const { expect } = chai; + +// The contract ABI of IMX on L1. +const IMX_ABI = `[{"inputs":[{"internalType":"address","name":"minter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]`; + +describe("Bridge e2e test", () => { + let rootProvider: providers.JsonRpcProvider; + let childProvider: providers.JsonRpcProvider; + let rootTestWallet: ethers.Wallet; + let childTestWallet: ethers.Wallet; + let rootPauserWallet: ethers.Wallet; + let childPauserWallet: ethers.Wallet; + let rootPrivilegedWallet: ethers.Wallet; + let childPrivilegedWallet: ethers.Wallet; + let rootBridge: ethers.Contract; + let rootWETH: ethers.Contract; + let rootIMX: ethers.Contract; + let childBridge: ethers.Contract; + let childETH: ethers.Contract; + let childWIMX: ethers.Contract; + let rootCustomToken: ethers.Contract; + let childCustomToken: ethers.Contract; + let axelarAPI: string; + + before(async function () { + this.timeout(300000); + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let testBreakGlassKey = requireEnv("BREAKGLASS_EOA_SECRET"); + let testPriviledgeKey = requireEnv("PRIVILEGED_EOA_SECRET"); + let testAccountKey = requireEnv("TEST_ACCOUNT_SECRET"); + let rootIMXAddr = requireEnv("ROOT_IMX_ADDR"); + let rootWETHAddr = requireEnv("ROOT_WETH_ADDR"); + axelarAPI = requireEnv("AXELAR_API_URL"); + + // Read from contract file. + let childContracts = getChildContracts(); + let childBridgeAddr = childContracts.CHILD_BRIDGE_ADDRESS; + let childWIMXAddr = childContracts.WRAPPED_IMX_ADDRESS; + let rootContracts = getRootContracts(); + let rootBridgeAddr = rootContracts.ROOT_BRIDGE_ADDRESS; + let rootCustomTokenAddr = rootContracts.ROOT_TEST_CUSTOM_TOKEN; + + rootProvider = new providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); + childProvider = new providers.JsonRpcProvider(childRPCURL, Number(childChainID)); + rootTestWallet = new ethers.Wallet(testAccountKey, rootProvider); + childTestWallet = new ethers.Wallet(testAccountKey, childProvider); + rootPauserWallet = new ethers.Wallet(testBreakGlassKey, rootProvider); + childPauserWallet = new ethers.Wallet(testBreakGlassKey, childProvider); + rootPrivilegedWallet = new ethers.Wallet(testPriviledgeKey, rootProvider); + childPrivilegedWallet = new ethers.Wallet(testPriviledgeKey, childProvider); + + rootBridge = getContract("RootERC20BridgeFlowRate", rootBridgeAddr, rootProvider); + rootWETH = getContract("WETH", rootWETHAddr, rootProvider); + rootIMX = new ethers.Contract(rootIMXAddr, IMX_ABI, rootProvider); + rootCustomToken = getContract("ChildERC20", rootCustomTokenAddr, rootProvider); + childBridge = getContract("ChildERC20Bridge", childBridgeAddr, childProvider); + childETH = getContract("ChildERC20", await childBridge.childETHToken(), childProvider); + childWIMX = getContract("WIMX", childWIMXAddr, childProvider); + + // Transfer 0.5 ETH to root pauser + let resp = await rootTestWallet.sendTransaction({ + to: rootPauserWallet.address, + value: ethers.utils.parseEther("0.5"), + }) + await waitForReceipt(resp.hash, rootProvider); + + // Transfer 0.5 ETH to root unpauser + resp = await rootTestWallet.sendTransaction({ + to: rootPrivilegedWallet.address, + value: ethers.utils.parseEther("0.5"), + }) + await waitForReceipt(resp.hash, rootProvider); + }) + + it("should not deposit IMX if allowance is insufficient", async() => { + let amt = ethers.utils.parseEther("50.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + let resp = await rootIMX.connect(rootTestWallet).approve(rootBridge.address, amt.sub(1)); + await waitForReceipt(resp.hash, rootProvider); + + // Fail to deposit on L1 + await expect(rootBridge.connect(rootTestWallet).deposit(rootIMX.address, amt, { + value: bridgeFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should not deposit IMX if balance is insufficient", async() => { + let balance = await rootIMX.balanceOf(rootTestWallet.address); + + let amt = balance.add(1); + let bridgeFee = ethers.utils.parseEther("0.001"); + + await expect( + depositIMX(rootTestWallet, amt, bridgeFee, null) + ).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + // Local only + it("should not deposit IMX if root bridge is paused", async() => { + let resp; + // Pause root bridge + if (!await rootBridge.paused()) { + resp = await rootBridge.connect(rootPauserWallet).pause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.true; + } + + // Try to deposit. + let amt = ethers.utils.parseEther("10.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + await expect( + depositIMX(rootTestWallet, amt, bridgeFee, null) + ).to.be.rejectedWith("Pausable: paused"); + + // Unpause root bridge + resp = await rootBridge.connect(rootPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.false; + }).timeout(2400000) + + // Local only + it("should not deposit IMX if deposit limit is reached", async() => { + let limit = ethers.utils.parseEther("100000000.0"); + + let amt = limit.add(1); + let bridgeFee = ethers.utils.parseEther("0.001"); + + await expect( + depositIMX(rootTestWallet, amt, bridgeFee, null) + ).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should successfully deposit IMX to self from L1 to L2", async() => { + // Get IMX balance on root & child chains before deposit + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childProvider.getBalance(childTestWallet.address); + + let amt = ethers.utils.parseEther("50.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + let resp = await depositIMX(rootTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childProvider.getBalance(childTestWallet.address); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.sub(amt); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully deposit IMX to others from L1 to L2", async() => { + let childRecipient = childPrivilegedWallet.address; + // Get IMX balance on root & child chains before deposit + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childProvider.getBalance(childRecipient); + + let amt = ethers.utils.parseEther("50.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + let resp = await depositIMX(rootTestWallet, amt, bridgeFee, childRecipient); + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childProvider.getBalance(childRecipient); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.sub(amt); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + // Local only + it("should not deposit IMX on L2 if child bridge is paused", async() => { + // Transfer 5 IMX to child pauser + let resp = await childTestWallet.sendTransaction({ + to: childPauserWallet.address, + value: ethers.utils.parseEther("5"), + }) + await waitForReceipt(resp.hash, childProvider); + + // Transfer 5 IMX to child unpauser + resp = await childTestWallet.sendTransaction({ + to: childPrivilegedWallet.address, + value: ethers.utils.parseEther("5"), + }) + await waitForReceipt(resp.hash, childProvider); + + // Pause child bridge + if (!await childBridge.paused()) { + resp = await childBridge.connect(childPauserWallet).pause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.true; + } + + // Get IMX balance on root & child chains before deposit + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childProvider.getBalance(childTestWallet.address); + + let amt = ethers.utils.parseEther("10.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + resp = await depositIMX(rootTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, rootProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + // Balance on L2 should not change. + await delay(10000); + let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postBalL2 = await childProvider.getBalance(childTestWallet.address); + + // Verify + let expectedPostL1 = preBalL1.sub(amt); + let expectedPostL2 = preBalL2; + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Unpause child bridge + resp = await childBridge.connect(childPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.false; + }).timeout(2400000) + + // Local only + it("should not withdraw IMX if child bridge is paused", async() => { + let resp; + // Pause child bridge + if (!await childBridge.paused()) { + resp = await childBridge.connect(childPauserWallet).pause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.true; + } + + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // IMX withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + await expect(childBridge.connect(childTestWallet).withdrawIMX(amt, { + value: amt.add(bridgeFee), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + })).to.be.rejectedWith("Pausable: paused"); + + // Unpause child bridge + resp = await childBridge.connect(childPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.false; + }).timeout(2400000) + + it("should not withdraw IMX if balance is insufficient", async() => { + let balance = await childProvider.getBalance(childTestWallet.address); + + let amt = balance; + let bridgeFee = ethers.utils.parseEther("1.0"); + + // IMX withdraw L2 to L1 + await expect( + withdrawIMX(childTestWallet, amt, bridgeFee, null) + ).to.be.rejectedWith("sender doesn't have enough funds to send tx"); + }).timeout(2400000) + + it("should successfully withdraw IMX to self from L2 to L1", async() => { + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childProvider.getBalance(childTestWallet.address); + + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // IMX withdraw L2 to L1 + let resp = await withdrawIMX(childTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childProvider.getBalance(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + await delay(1000); + } + + // Verify + let receipt = await childProvider.getTransactionReceipt(resp.hash); + let txFee = receipt.gasUsed.mul(receipt.effectiveGasPrice); + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(txFee).sub(amt).sub(bridgeFee); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully withdraw IMX to others from L2 to L1", async() => { + let rootRecipient = rootPrivilegedWallet.address; + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootRecipient); + let preBalL2 = await childProvider.getBalance(childTestWallet.address); + + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // IMX withdraw L2 to L1 + let resp = await withdrawIMX(childTestWallet, amt, bridgeFee, rootRecipient); + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childProvider.getBalance(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootIMX.balanceOf(rootRecipient); + await delay(1000); + } + + // Verify + let receipt = await childProvider.getTransactionReceipt(resp.hash); + let txFee = receipt.gasUsed.mul(receipt.effectiveGasPrice); + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(txFee).sub(amt).sub(bridgeFee); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + // Local only + it("should not withdraw IMX on L1 if root bridge is paused", async() => { + let resp; + // Pause root bridge + if (!await rootBridge.paused()) { + resp = await rootBridge.connect(rootPauserWallet).pause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.true; + } + + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childProvider.getBalance(childTestWallet.address); + + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // IMX withdraw L2 to L1 + resp = await withdrawIMX(childTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + // Balance on L1 should not change. + await delay(10000); + let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postBalL2 = await childProvider.getBalance(childTestWallet.address); + + // Verify + let receipt = await childProvider.getTransactionReceipt(resp.hash); + let txFee = receipt.gasUsed.mul(receipt.effectiveGasPrice); + let expectedPostL1 = preBalL1; + let expectedPostL2 = preBalL2.sub(txFee).sub(amt).sub(bridgeFee); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Unpause root bridge + resp = await rootBridge.connect(rootPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.false; + }).timeout(2400000) + + // Local only + it("should put IMX withdrawal in pending when violating rate limit policy", async() => { + // Set new rate limit + let resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(rootIMX.address, ethers.utils.parseEther("2.016"), ethers.utils.parseEther("0.00056"), ethers.utils.parseEther("1.008")); + await waitForReceipt(resp.hash, rootProvider); + + // Withdraw of IMX exceeding large threshold + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childProvider.getBalance(childTestWallet.address); + let preLength = await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address); + + let amt1 = ethers.utils.parseEther("1.1"); + let bridgeFee1 = ethers.utils.parseEther("1.0"); + + // IMX withdraw L2 to L1 + resp = await withdrawIMX(childTestWallet, amt1, bridgeFee1, null); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + let receipt = await childProvider.getTransactionReceipt(resp.hash); + let txFee1 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength)) { + await delay(1000); + } + + // Withdraw of IMX exceeding rate limit + let amt2 = ethers.utils.parseEther("1.0"); + let bridgeFee2 = ethers.utils.parseEther("1.0"); + + // IMX withdraw L2 to L1 + resp = await withdrawIMX(childTestWallet, amt2, bridgeFee2, null); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + receipt = await childProvider.getTransactionReceipt(resp.hash); + let txFee2 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength.add(1))) { + await delay(1000); + } + + // Try to withdraw + await expect(rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1))).to.be.rejectedWith( + "UNPREDICTABLE_GAS_LIMIT" + ); + + // Fast-forward to 24 hours later. + await rootProvider.send( + "hardhat_mine", [ + "0x15181", // 24 hours + ]); + + // Withdraw again + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1)) + await waitForReceipt(resp.hash, rootProvider); + + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength) + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postBalL2 = await childProvider.getBalance(childTestWallet.address); + + // Verify + let expectedPostL1 = preBalL1.add(amt1).add(amt2); + let expectedPostL2 = preBalL2.sub(amt1).sub(amt2).sub(txFee1).sub(txFee2).sub(bridgeFee1).sub(bridgeFee2); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Recover rate limit + resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(rootIMX.address, ethers.utils.parseEther("15516"), ethers.utils.parseEther("4.31"), ethers.utils.parseEther("7758")); + await waitForReceipt(resp.hash, rootProvider); + + // Deactive withdraw queue + resp = await rootBridge.connect(rootPrivilegedWallet).deactivateWithdrawalQueue(); + await waitForReceipt(resp.hash, rootProvider); + }).timeout(2400000) + + // Local only + it("should not withdraw WIMX if child bridge is paused", async() => { + let resp; + // Pause child bridge + if (!await childBridge.paused()) { + resp = await childBridge.connect(childPauserWallet).pause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.true; + } + + // Wrap 1 IMX + let [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).deposit({ + value: ethers.utils.parseEther("1.0"), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Approve + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt, { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + await expect(childBridge.connect(childTestWallet).withdrawWIMX(amt, { + value: amt.add(bridgeFee), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + })).to.be.rejectedWith("Pausable: paused"); + + // Unpause child bridge + resp = await childBridge.connect(childPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.false; + }).timeout(2400000) + + it("should not withdraw wIMX if allowance is insufficient", async() => { + // Wrap 1 IMX + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childWIMX.connect(childTestWallet).deposit({ + value: ethers.utils.parseEther("1.0"), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Approve + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt.sub(1), { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // wIMX withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + await expect(childBridge.connect(childTestWallet).withdrawWIMX(amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should not withdraw wIMX if balance is insufficient", async() => { + let balance = await childWIMX.balanceOf(childTestWallet.address); + + let amt = balance; + let bridgeFee = ethers.utils.parseEther("1.0"); + + // wIMX withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + await expect(childBridge.connect(childTestWallet).withdrawWIMX(amt.add(1), { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should successfully withdraw wIMX to self from L2 to L1", async() => { + // Wrap 1 IMX + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childWIMX.connect(childTestWallet).deposit({ + value: ethers.utils.parseEther("1.0"), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childWIMX.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Approve + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt, { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // wIMX withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdrawWIMX(amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childWIMX.balanceOf(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully withdraw wIMX to others from L2 to L1", async() => { + let rootRecipient = rootPrivilegedWallet.address; + // Wrap 1 IMX + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childWIMX.connect(childTestWallet).deposit({ + value: ethers.utils.parseEther("1.0"), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootRecipient); + let preBalL2 = await childWIMX.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Approve + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt, { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // wIMX withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdrawWIMXTo(rootRecipient, amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childWIMX.balanceOf(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootIMX.balanceOf(rootRecipient); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + // Local only + it("should not withdraw wIMX on L1 if root bridge is paused", async() => { + let resp; + // Pause root bridge + if (!await rootBridge.paused()) { + resp = await rootBridge.connect(rootPauserWallet).pause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.true; + } + + // Wrap 1 IMX + let [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).deposit({ + value: ethers.utils.parseEther("1.0"), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childWIMX.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Approve + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt, { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // wIMX withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdrawWIMX(amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + // Balance on L1 should not change. + await delay(10000); + let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postBalL2 = await childWIMX.balanceOf(childTestWallet.address); + + // Verify + let expectedPostL1 = preBalL1; + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Unpause root bridge + resp = await rootBridge.connect(rootPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.false; + }).timeout(2400000) + + // Local only + it("should put wIMX withdrawal in pending when violating rate limit policy", async() => { + // Set new rate limit + let resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(rootIMX.address, ethers.utils.parseEther("2.016"), ethers.utils.parseEther("0.00056"), ethers.utils.parseEther("1.008")); + await waitForReceipt(resp.hash, rootProvider); + + // Wrap 3 IMX + let [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).deposit({ + value: ethers.utils.parseEther("3.0"), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // Withdraw of IMX exceeding large threshold + // Get IMX balance on root & child chains before withdraw + let preBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preBalL2 = await childWIMX.balanceOf(childTestWallet.address); + let preLength = await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address); + + let amt1 = ethers.utils.parseEther("1.1"); + let bridgeFee1 = ethers.utils.parseEther("1.0"); + + // Approve + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt1, { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // wIMX withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdrawWIMX(amt1, { + value: bridgeFee1, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength)) { + await delay(1000); + } + + // Withdraw of IMX exceeding rate limit + let amt2 = ethers.utils.parseEther("1.0"); + let bridgeFee2 = ethers.utils.parseEther("1.0"); + + // Approve + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amt2, { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + + // IMX withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdrawWIMX(amt2, { + value: bridgeFee2, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength.add(1))) { + await delay(1000); + } + + // Try to withdraw + await expect(rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1))).to.be.rejectedWith( + "UNPREDICTABLE_GAS_LIMIT" + ); + + // Fast-forward to 24 hours later. + await rootProvider.send( + "hardhat_mine", [ + "0x15181", // 24 hours + ]); + + // Withdraw again + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1)) + await waitForReceipt(resp.hash, rootProvider); + + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength) + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postBalL2 = await childWIMX.balanceOf(childTestWallet.address); + + // Verify + let expectedPostL1 = preBalL1.add(amt1).add(amt2); + let expectedPostL2 = preBalL2.sub(amt1).sub(amt2); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Recover rate limit + resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(rootIMX.address, ethers.utils.parseEther("15516"), ethers.utils.parseEther("4.31"), ethers.utils.parseEther("7758")); + await waitForReceipt(resp.hash, rootProvider); + + // Deactive withdraw queue + resp = await rootBridge.connect(rootPrivilegedWallet).deactivateWithdrawalQueue(); + await waitForReceipt(resp.hash, rootProvider); + }).timeout(2400000) + + it("should not deposit ETH if balance is insufficient", async() => { + let balance = await rootProvider.getBalance(rootTestWallet.address); + + let amt = balance; + let bridgeFee = ethers.utils.parseEther("0.001"); + + await expect(rootBridge.connect(rootTestWallet).depositETH(amt, { + value: amt.add(bridgeFee), + })).to.be.rejectedWith("sender doesn't have enough funds to send tx"); + }).timeout(2400000) + + // Local only + it("should not deposit ETH if root bridge is paused", async() => { + let resp; + // Pause root bridge + if (!await rootBridge.paused()) { + resp = await rootBridge.connect(rootPauserWallet).pause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.true; + } + + let amt = ethers.utils.parseEther("0.001"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Fail to deposit on L1 + await expect( + depositETH(rootTestWallet, amt, bridgeFee, null) + ).to.be.rejectedWith("Pausable: paused"); + + // Unpause root bridge + resp = await rootBridge.connect(rootPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.false; + }).timeout(2400000) + + it("should successfully deposit ETH to self from L1 to L2", async() => { + // Get ETH balance on root & child chains before deposit + let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // ETH deposit L1 to L2 + let resp = await depositETH(rootTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childETH.balanceOf(childTestWallet.address); + await delay(1000); + } + + // Verify + let receipt = await rootProvider.getTransactionReceipt(resp.hash); + let txFee = receipt.gasUsed.mul(receipt.effectiveGasPrice); + let expectedPostL1 = preBalL1.sub(txFee).sub(amt).sub(bridgeFee); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully deposit ETH to others from L1 to L2", async() => { + let childRecipient = childPrivilegedWallet.address; + // Get ETH balance on root & child chains before deposit + let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childRecipient); + + let amt = ethers.utils.parseEther("0.001"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // ETH deposit L1 to L2 + let resp = await depositETH(rootTestWallet, amt, bridgeFee, childRecipient); + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childETH.balanceOf(childRecipient); + await delay(1000); + } + + // Verify + let receipt = await rootProvider.getTransactionReceipt(resp.hash); + let txFee = receipt.gasUsed.mul(receipt.effectiveGasPrice); + let expectedPostL1 = preBalL1.sub(txFee).sub(amt).sub(bridgeFee); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + // Local only + it("should not deposit ETH on L2 if child bridge is paused", async() => { + let resp; + // Pause child bridge + if (!await childBridge.paused()) { + resp = await childBridge.connect(childPauserWallet).pause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.true; + } + + // Get ETH balance on root & child chains before deposit + let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.001"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Try to deposit + resp = await depositETH(rootTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, rootProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + // Balance on L2 should not change. + await delay(10000); + let postBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let postBalL2 = await childETH.balanceOf(childTestWallet.address); + + // Verify + let receipt = await rootProvider.getTransactionReceipt(resp.hash); + let txFee = receipt.gasUsed.mul(receipt.effectiveGasPrice); + let expectedPostL1 = preBalL1.sub(txFee).sub(amt).sub(bridgeFee); + let expectedPostL2 = preBalL2; + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Unpause child bridge + resp = await childBridge.connect(childPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.false; + }).timeout(2400000) + + it("should successfully deposit wETH to self from L1 to L2", async() => { + // Wrap 0.01 ETH + let resp = await rootWETH.connect(rootTestWallet).deposit({ + value: ethers.utils.parseEther("0.01"), + }) + await waitForReceipt(resp.hash, rootProvider); + + // Get ETH balance on root & child chains before withdraw + let preBalL1 = await rootWETH.balanceOf(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.001"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + resp = await rootWETH.connect(rootTestWallet).approve(rootBridge.address, amt); + await waitForReceipt(resp.hash, rootProvider); + + // wETH deposit L1 to L2 + resp = await rootBridge.connect(rootTestWallet).deposit(rootWETH.address, amt, { + value: bridgeFee, + }) + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootWETH.balanceOf(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childETH.balanceOf(childTestWallet.address); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.sub(amt); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully deposit wETH to others from L1 to L2", async() => { + let childRecipient = childPrivilegedWallet.address; + // Wrap 0.01 ETH + let resp = await rootWETH.connect(rootTestWallet).deposit({ + value: ethers.utils.parseEther("0.01"), + }) + await waitForReceipt(resp.hash, rootProvider); + + // Get ETH balance on root & child chains before withdraw + let preBalL1 = await rootWETH.balanceOf(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childRecipient); + + let amt = ethers.utils.parseEther("0.001"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + resp = await rootWETH.connect(rootTestWallet).approve(rootBridge.address, amt); + await waitForReceipt(resp.hash, rootProvider); + + // wETH deposit L1 to L2 + resp = await rootBridge.connect(rootTestWallet).depositTo(rootWETH.address, childRecipient, amt, { + value: bridgeFee, + }) + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootWETH.balanceOf(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childETH.balanceOf(childRecipient); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.sub(amt); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + // Local only + it("should not withdraw ETH if child bridge is paused", async() => { + let resp; + // Pause child bridge + if (!await childBridge.paused()) { + resp = await childBridge.connect(childPauserWallet).pause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.true; + } + + let amt = ethers.utils.parseEther("0.0005"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + await expect( + withdrawETH(childTestWallet, amt, bridgeFee, null) + ).to.be.rejectedWith("Pausable: paused"); + + // Unpause child bridge + resp = await childBridge.connect(childPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.false; + }).timeout(2400000) + + it("should not withdraw ETH if balance is insufficient", async() => { + let amt = await childETH.balanceOf(childTestWallet.address); + amt = amt.add(1); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + await expect( + withdrawETH(childTestWallet, amt, bridgeFee, null) + ).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should successfully withdraw ETH to self from L2 to L1", async() => { + // Get ETH balance on root & child chains before withdraw + let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.0005"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + let resp = await withdrawETH(childTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childETH.balanceOf(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootProvider.getBalance(rootTestWallet.address); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully withdraw ETH to others from L2 to L1", async() => { + let rootRecipient = rootPrivilegedWallet.address; + // Get ETH balance on root & child chains before withdraw + let preBalL1 = await rootProvider.getBalance(rootRecipient); + let preBalL2 = await childETH.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.0005"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + let resp = await withdrawETH(childTestWallet, amt, bridgeFee, rootRecipient); + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childETH.balanceOf(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootProvider.getBalance(rootRecipient); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + // Local only + it("should not withdraw ETH on L1 if root bridge is paused", async() => { + let resp; + // Pause root bridge + if (!await rootBridge.paused()) { + resp = await rootBridge.connect(rootPauserWallet).pause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.true; + } + + // Get ETH balance on root & child chains before withdraw + let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.0005"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + resp = await withdrawETH(childTestWallet, amt, bridgeFee, null); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + // Balance on L1 should not change. + await delay(10000); + let postBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let postBalL2 = await childETH.balanceOf(childTestWallet.address); + + // Verify + let expectedPostL1 = preBalL1; + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Unpause root bridge + resp = await rootBridge.connect(rootPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.false; + }).timeout(2400000) + + // Local only + it("should put ETH withdrawal in pending when violating rate limit policy", async() => { + // Set new rate limit + let resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(await rootBridge.NATIVE_ETH(), ethers.utils.parseEther("0.0010008"), ethers.utils.parseEther("0.000000278"), ethers.utils.parseEther("0.0005004")); + await waitForReceipt(resp.hash, rootProvider); + + // Withdraw of ETH exceeding large threshold + // Get ETH balance on root & child chains before withdraw + let preBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let preBalL2 = await childETH.balanceOf(childTestWallet.address); + let preLength = await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address); + + let amt1 = ethers.utils.parseEther("0.0006"); + let bridgeFee1 = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdrawETH(amt1, { + value: bridgeFee1, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength)) { + await delay(1000); + } + + // Withdraw of ETH exceeding rate limit + let amt2 = ethers.utils.parseEther("0.0005"); + let bridgeFee2 = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdrawETH(amt2, { + value: bridgeFee2, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength.add(1))) { + await delay(1000); + } + + // Try to withdraw + await expect(rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1))).to.be.rejectedWith( + "UNPREDICTABLE_GAS_LIMIT" + ); + + // Fast-forward to 24 hours later. + await rootProvider.send( + "hardhat_mine", [ + "0x15181", // 24 hours + ]); + + // Withdraw again + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1)) + await waitForReceipt(resp.hash, rootProvider); + let receipt = await rootProvider.getTransactionReceipt(resp.hash); + let txFee1 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength) + await waitForReceipt(resp.hash, rootProvider); + receipt = await rootProvider.getTransactionReceipt(resp.hash); + let txFee2 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + + let postBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let postBalL2 = await childETH.balanceOf(childTestWallet.address); + + // Verify + let expectedPostL1 = preBalL1.sub(txFee1).sub(txFee2).add(amt1).add(amt2); + let expectedPostL2 = preBalL2.sub(amt1).sub(amt2); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Recover rate limit + resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(await rootBridge.NATIVE_ETH(), ethers.utils.parseEther("10.08"), ethers.utils.parseEther("0.0028"), ethers.utils.parseEther("5.04")); + await waitForReceipt(resp.hash, rootProvider); + + // Deactive withdraw queue + resp = await rootBridge.connect(rootPrivilegedWallet).deactivateWithdrawalQueue(); + await waitForReceipt(resp.hash, rootProvider); + }).timeout(2400000) + + it("should not deposit unmapped token", async() => { + let unMappedToken = ethers.utils.getAddress("0xccC8cb5229B0ac8069C51fd58367Fd1e622aFD97"); + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Token deposit L1 to L2 + await expect(rootBridge.connect(rootTestWallet).deposit(unMappedToken, amt, { + value: bridgeFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should successfully map a ERC20 Token", async() => { + let childContracts = getChildContracts(); + let childCustomTokenAddr = childContracts.CHILD_TEST_CUSTOM_TOKEN; + if (childCustomTokenAddr != "") { + childCustomToken = getContract("ChildERC20", childCustomTokenAddr, childProvider); + console.log("Custom token has already been mapped, skip."); + return; + } + // Map token + let bridgeFee = ethers.utils.parseEther("0.001"); + let expectedChildTokenAddr = await rootBridge.callStatic.mapToken(rootCustomToken.address, { + value: bridgeFee, + }); + let resp = await rootBridge.connect(rootTestWallet).mapToken(rootCustomToken.address, { + value: bridgeFee, + }) + await waitForReceipt(resp.hash, rootProvider); + + let childTokenAddr = await childBridge.rootTokenToChildToken(rootCustomToken.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (childTokenAddr == ethers.constants.AddressZero) { + childTokenAddr = await childBridge.rootTokenToChildToken(rootCustomToken.address); + await delay(1000); + } + childCustomToken = getContract("ChildERC20", childTokenAddr, childProvider); + childContracts.CHILD_TEST_CUSTOM_TOKEN = childTokenAddr; + saveChildContracts(childContracts); + + // Verify + expect(childTokenAddr).to.equal(expectedChildTokenAddr); + }).timeout(2400000) + + it("should not map a mapped ERC20 Token", async() => { + let childContracts = getChildContracts(); + let childCustomTokenAddr = childContracts.CHILD_TEST_CUSTOM_TOKEN; + if (childCustomTokenAddr == "") { + childCustomToken = getContract("ChildERC20", childCustomTokenAddr, childProvider); + console.log("Custom token has not been mapped yet, skip."); + return; + } + // Map token + let bridgeFee = ethers.utils.parseEther("0.001"); + await expect(rootBridge.connect(rootTestWallet).mapToken(rootCustomToken.address, { + value: bridgeFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should not deposit mapped ERC20 Token if allowance is insufficient", async() => { + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + let resp = await rootCustomToken.connect(rootTestWallet).approve(rootBridge.address, amt.sub(1)); + await waitForReceipt(resp.hash, rootProvider); + + // Fail to deposit on L1 + await expect(rootBridge.connect(rootTestWallet).deposit(rootCustomToken.address, amt, { + value: bridgeFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should not deposit mapped ERC20 Token if balance is insufficient", async() => { + let balance = await rootCustomToken.balanceOf(rootTestWallet.address); + + let amt = balance.add(1); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + let resp = await rootCustomToken.connect(rootTestWallet).approve(rootBridge.address, amt); + await waitForReceipt(resp.hash, rootProvider); + + await expect(rootBridge.connect(rootTestWallet).deposit(rootCustomToken.address, amt, { + value: bridgeFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + // Local only + it("should not deposit mapped ERC20 Token if root bridge is paused", async() => { + // Transfer 0.1 ETH to root pauser + let resp = await rootTestWallet.sendTransaction({ + to: rootPauserWallet.address, + value: ethers.utils.parseEther("0.1"), + }) + await waitForReceipt(resp.hash, rootProvider); + + // Transfer 0.1 ETH to root unpauser + resp = await rootTestWallet.sendTransaction({ + to: rootPrivilegedWallet.address, + value: ethers.utils.parseEther("0.1"), + }) + await waitForReceipt(resp.hash, rootProvider); + + // Pause root bridge + if (!await rootBridge.paused()) { + resp = await rootBridge.connect(rootPauserWallet).pause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.true; + } + + // Try to deposit. + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + resp = await rootCustomToken.connect(rootTestWallet).approve(rootBridge.address, amt); + await waitForReceipt(resp.hash, rootProvider); + + // Fail to deposit on L1 + await expect(rootBridge.connect(rootTestWallet).deposit(rootCustomToken.address, amt, { + value: bridgeFee, + })).to.be.rejectedWith("Pausable: paused"); + + // Unpause root bridge + resp = await rootBridge.connect(rootPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, rootProvider); + expect(await rootBridge.paused()).to.false; + }).timeout(2400000) + + it("should successfully deposit mapped ERC20 Token to self from L1 to L2", async() => { + // Get token balance on root & child chains before deposit + let preBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let preBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("10.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + let resp = await rootCustomToken.connect(rootTestWallet).approve(rootBridge.address, amt); + await waitForReceipt(resp.hash, rootProvider); + + // Token deposit L1 to L2 + resp = await rootBridge.connect(rootTestWallet).deposit(rootCustomToken.address, amt, { + value: bridgeFee, + }) + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.sub(amt); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully deposit mapped ERC20 Token to others from L1 to L2", async() => { + let childRecipient = childPrivilegedWallet.address; + // Get token balance on root & child chains before deposit + let preBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let preBalL2 = await childCustomToken.balanceOf(childRecipient); + + let amt = ethers.utils.parseEther("1.0"); + let bridgeFee = ethers.utils.parseEther("0.001"); + + // Approve + let resp = await rootCustomToken.connect(rootTestWallet).approve(rootBridge.address, amt); + await waitForReceipt(resp.hash, rootProvider); + + // Token deposit L1 to L2 + resp = await rootBridge.connect(rootTestWallet).depositTo(rootCustomToken.address, childRecipient, amt, { + value: bridgeFee, + }) + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let postBalL2 = preBalL2; + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL2.eq(preBalL2)) { + postBalL2 = await childCustomToken.balanceOf(childRecipient); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.sub(amt); + let expectedPostL2 = preBalL2.add(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should not withdraw unmapped token", async() => { + let unMappedToken = ethers.utils.getAddress("0xccC8cb5229B0ac8069C51fd58367Fd1e622aFD97"); + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Token withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + await expect(childBridge.connect(childTestWallet).withdraw(unMappedToken, amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + // Local only + it("should not withdraw mapped ERC20 Token if child bridge is paused", async() => { + // Transfer 0.1 IMX to child pauser + let resp = await childTestWallet.sendTransaction({ + to: childPauserWallet.address, + value: ethers.utils.parseEther("0.1"), + }) + await waitForReceipt(resp.hash, childProvider); + + // Transfer 0.1 IMX to child unpauser + resp = await childTestWallet.sendTransaction({ + to: childPrivilegedWallet.address, + value: ethers.utils.parseEther("0.1"), + }) + await waitForReceipt(resp.hash, childProvider); + + // Pause child bridge + if (!await childBridge.paused()) { + resp = await childBridge.connect(childPauserWallet).pause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.true; + } + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Token withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + await expect(childBridge.connect(childTestWallet).withdraw(childCustomToken.address, amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + })).to.be.rejectedWith("Pausable: paused"); + + // Unpause child bridge + resp = await childBridge.connect(childPrivilegedWallet).unpause(); + await waitForReceipt(resp.hash, childProvider); + expect(await childBridge.paused()).to.false; + }).timeout(2400000) + + it("should not withdraw mapped ERC20 Token if balance is insufficient", async() => { + let amt = await childCustomToken.balanceOf(childTestWallet.address); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // ETH withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + await expect(childBridge.connect(childTestWallet).withdraw(childCustomToken.address, amt.add(1), { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + })).to.be.rejectedWith("UNPREDICTABLE_GAS_LIMIT"); + }).timeout(2400000) + + it("should successfully withdraw mapped ERC20 Token to self from L2 to L1", async() => { + // Get token balance on root & child chains before deposit + let preBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let preBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Token withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childBridge.connect(childTestWallet).withdraw(childCustomToken.address, amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }) + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + it("should successfully withdraw mapped ERC20 Token to others from L2 to L1", async() => { + let rootRecipient = rootPrivilegedWallet.address; + // Get token balance on root & child chains before deposit + let preBalL1 = await rootCustomToken.balanceOf(rootRecipient); + let preBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + let amt = ethers.utils.parseEther("0.5"); + let bridgeFee = ethers.utils.parseEther("1.0"); + + // Token withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + let resp = await childBridge.connect(childTestWallet).withdrawTo(childCustomToken.address, rootRecipient, amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }) + await waitForReceipt(resp.hash, childProvider); + + let postBalL1 = preBalL1; + let postBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + await waitUntilSucceed(axelarAPI, resp.hash); + + while (postBalL1.eq(preBalL1)) { + postBalL1 = await rootCustomToken.balanceOf(rootRecipient); + await delay(1000); + } + + // Verify + let expectedPostL1 = preBalL1.add(amt); + let expectedPostL2 = preBalL2.sub(amt); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + }).timeout(2400000) + + // Local only + it("should put mapped ERC20 Token withdrawal in pending when violating rate limit policy", async() => { + // Set new rate limit + let resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(rootCustomToken.address, ethers.utils.parseEther("1.0008"), ethers.utils.parseEther("0.000278"), ethers.utils.parseEther("0.5004")); + await waitForReceipt(resp.hash, rootProvider); + + // Withdraw of ERC20 exceeding large threshold + // Get ERC20 balance on root & child chains before withdraw + let preBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let preBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + let preLength = await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address); + + let amt1 = ethers.utils.parseEther("0.6"); + let bridgeFee1 = ethers.utils.parseEther("1.0"); + + // ERC20 withdraw L2 to L1 + let [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdraw(childCustomToken.address, amt1, { + value: bridgeFee1, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength)) { + await delay(1000); + } + + // Withdraw of ERC20 exceeding rate limit + let amt2 = ethers.utils.parseEther("0.5"); + let bridgeFee2 = ethers.utils.parseEther("1.0"); + + // ERC20 withdraw L2 to L1 + [priorityFee, maxFee] = await getFee(childProvider); + resp = await childBridge.connect(childTestWallet).withdraw(childCustomToken.address, amt2, { + value: bridgeFee2, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + await waitForReceipt(resp.hash, childProvider); + await waitUntilSucceed(axelarAPI, resp.hash); + + while ((await rootBridge.getPendingWithdrawalsLength(rootTestWallet.address)).eq(preLength.add(1))) { + await delay(1000); + } + + // Try to withdraw + await expect(rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1))).to.be.rejectedWith( + "UNPREDICTABLE_GAS_LIMIT" + ); + + // Fast-forward to 24 hours later. + await rootProvider.send( + "hardhat_mine", [ + "0x15181", // 24 hours + ]); + + // Withdraw again + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength.add(1)) + await waitForReceipt(resp.hash, rootProvider); + + resp = await rootBridge.connect(rootTestWallet).finaliseQueuedWithdrawal(rootTestWallet.address, preLength) + await waitForReceipt(resp.hash, rootProvider); + + let postBalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let postBalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + // Verify + let expectedPostL1 = preBalL1.add(amt1).add(amt2); + let expectedPostL2 = preBalL2.sub(amt1).sub(amt2); + expect(postBalL1.toBigInt()).to.equal(expectedPostL1.toBigInt()); + expect(postBalL2.toBigInt()).to.equal(expectedPostL2.toBigInt()); + + // Recover rate limit + resp = await rootBridge.connect(rootPrivilegedWallet).setRateControlThreshold(rootCustomToken.address, ethers.utils.parseEther("20016.0"), ethers.utils.parseEther("5.56"), ethers.utils.parseEther("10008.0")); + await waitForReceipt(resp.hash, rootProvider); + + // Deactive withdraw queue + resp = await rootBridge.connect(rootPrivilegedWallet).deactivateWithdrawalQueue(); + await waitForReceipt(resp.hash, rootProvider); + }).timeout(2400000) + + // Local only + it("should successfully process multiple deposit and withdrawal requests in parallel", async() => { + // Deposit & withdrawal amount + let amtL1 = ethers.utils.parseEther("1.0"); + let bridgeFeeL1 = ethers.utils.parseEther("0.001"); + let amtL2 = ethers.utils.parseEther("0.5"); + let bridgeFeeL2 = ethers.utils.parseEther("1.0"); + + // Wrap & Approval + let resp = await rootIMX.connect(rootTestWallet).approve(rootBridge.address, amtL1); + await waitForReceipt(resp.hash, rootProvider); + + resp = await rootWETH.connect(rootTestWallet).deposit({ value: amtL1 }); + await waitForReceipt(resp.hash, rootProvider); + resp = await rootWETH.connect(rootTestWallet).approve(rootBridge.address, amtL1); + await waitForReceipt(resp.hash, rootProvider); + + resp = await rootCustomToken.connect(rootTestWallet).approve(rootBridge.address, amtL1); + await waitForReceipt(resp.hash, rootProvider); + + resp = await childWIMX.connect(childTestWallet).deposit( {value: amtL2 }); + await waitForReceipt(resp.hash, childProvider); + resp = await childWIMX.connect(childTestWallet).approve(childBridge.address, amtL2); + await waitForReceipt(resp.hash, childProvider); + + // Deposit IMX, ETH, WETH, ERC20, and withdraw IMX, WIMX, ETH, ERC20 + let preIMXBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let preETHBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let preWETHBalL1 = await rootWETH.balanceOf(rootTestWallet.address); + let preERC20BalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let preIMXBalL2 = await childProvider.getBalance(childTestWallet.address); + let preWIMXBalL2 = await childWIMX.balanceOf(childTestWallet.address); + let preETHBalL2 = await childETH.balanceOf(childTestWallet.address); + let preERC20BalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + // Stop mining + await rootProvider.send( + "evm_setIntervalMining", [ + 0, + ]); + await childProvider.send( + "evm_setIntervalMining", [ + 0, + ]); + + // Calls on L1 & L2 + let resp1 = await rootBridge.connect(rootTestWallet).deposit(rootIMX.address, amtL1, { + value: bridgeFeeL1, + }); + let resp2 = await rootBridge.connect(rootTestWallet).depositETH(amtL1, { + value: bridgeFeeL1.add(amtL1), + }); + let resp3 = await rootBridge.connect(rootTestWallet).deposit(rootWETH.address, amtL1, { + value: bridgeFeeL1, + }); + let resp4 = await rootBridge.connect(rootTestWallet).deposit(rootCustomToken.address, amtL1, { + value: bridgeFeeL1, + }); + let resp5 = await childBridge.connect(childTestWallet).withdrawIMX(amtL2, { + value: bridgeFeeL2.add(amtL2), + }); + let resp6 = await childBridge.connect(childTestWallet).withdrawWIMX(amtL2, { + value: bridgeFeeL2, + }); + let resp7 = await childBridge.connect(childTestWallet).withdrawETH(amtL2, { + value: bridgeFeeL2, + }); + let resp8 = await childBridge.connect(childTestWallet).withdraw(childCustomToken.address, amtL2, { + value: bridgeFeeL2, + }); + + + // Enable mining + await rootProvider.send( + "evm_setIntervalMining", [ + 1200, + ]); + await childProvider.send( + "evm_setIntervalMining", [ + 200, + ]); + + // Wait for transactions to be mined. + await waitForReceipt(resp1.hash, rootProvider); + await waitForReceipt(resp2.hash, rootProvider); + await waitForReceipt(resp3.hash, rootProvider); + await waitForReceipt(resp4.hash, rootProvider); + await waitForReceipt(resp5.hash, childProvider); + await waitForReceipt(resp6.hash, childProvider); + await waitForReceipt(resp7.hash, childProvider); + await waitForReceipt(resp8.hash, childProvider); + + // Wait for 30 seconds + await delay(30000); + let receipt = await rootProvider.getTransactionReceipt(resp1.hash); + let txFee1 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + receipt = await rootProvider.getTransactionReceipt(resp2.hash); + let txFee2 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + receipt = await rootProvider.getTransactionReceipt(resp3.hash); + let txFee3 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + receipt = await rootProvider.getTransactionReceipt(resp4.hash); + let txFee4 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + receipt = await childProvider.getTransactionReceipt(resp5.hash); + let txFee5 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + receipt = await childProvider.getTransactionReceipt(resp6.hash); + let txFee6 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + receipt = await childProvider.getTransactionReceipt(resp7.hash); + let txFee7 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + receipt = await childProvider.getTransactionReceipt(resp8.hash); + let txFee8 = receipt.gasUsed.mul(receipt.effectiveGasPrice); + + let postIMXBalL1 = await rootIMX.balanceOf(rootTestWallet.address); + let postETHBalL1 = await rootProvider.getBalance(rootTestWallet.address); + let postWETHBalL1 = await rootWETH.balanceOf(rootTestWallet.address); + let postERC20BalL1 = await rootCustomToken.balanceOf(rootTestWallet.address); + let postIMXBalL2 = await childProvider.getBalance(childTestWallet.address); + let postWIMXBalL2 = await childWIMX.balanceOf(childTestWallet.address); + let postETHBalL2 = await childETH.balanceOf(childTestWallet.address); + let postERC20BalL2 = await childCustomToken.balanceOf(childTestWallet.address); + + // Verify + let expectedIMXBalL1 = preIMXBalL1.sub(amtL1).add(amtL2.mul(2)); + let expectedETHBalL1 = preETHBalL1.sub(amtL1).add(amtL2).sub(bridgeFeeL1.mul(4)).sub(txFee1).sub(txFee2).sub(txFee3).sub(txFee4); + let expectedWETHBalL1 = preWETHBalL1.sub(amtL1); + let expectedERC20BalL1 = preERC20BalL1.sub(amtL1).add(amtL2); + let expectedIMXBalL2 = preIMXBalL2.add(amtL1).sub(amtL2).sub(bridgeFeeL2.mul(4)).sub(txFee5).sub(txFee6).sub(txFee7).sub(txFee8); + let expectedWIMXBalL2 = preWIMXBalL2.sub(amtL2); + let expectedETHBalL2 = preETHBalL2.add(amtL1.mul(2)).sub(amtL2); + let expectedERC20BalL2 = preERC20BalL2.add(amtL1).sub(amtL2); + expect(postIMXBalL1.toBigInt()).to.equal(expectedIMXBalL1.toBigInt()); + expect(postETHBalL1.toBigInt()).to.equal(expectedETHBalL1.toBigInt()); + expect(postWETHBalL1.toBigInt()).to.equal(expectedWETHBalL1.toBigInt()); + expect(postERC20BalL1.toBigInt()).to.equal(expectedERC20BalL1.toBigInt()); + expect(postIMXBalL2.toBigInt()).to.equal(expectedIMXBalL2.toBigInt()); + expect(postWIMXBalL2.toBigInt()).to.equal(expectedWIMXBalL2.toBigInt()); + expect(postETHBalL2.toBigInt()).to.equal(expectedETHBalL2.toBigInt()); + expect(postERC20BalL2.toBigInt()).to.equal(expectedERC20BalL2.toBigInt()); + + // Test balance. + }).timeout(2400000) + + async function depositIMX(sender: ethers.Wallet, amt: ethers.BigNumber, bridgeFee: ethers.BigNumber, recipient: string | null) { + // Approve + let resp = await rootIMX.connect(rootTestWallet).approve(rootBridge.address, amt); + await waitForReceipt(resp.hash, rootProvider); + + if (recipient == null) { + return rootBridge.connect(sender).deposit(rootIMX.address, amt, { + value: bridgeFee, + }); + } else { + return rootBridge.connect(sender).depositTo(rootIMX.address, recipient, amt, { + value: bridgeFee, + }); + } + } + + async function withdrawIMX(sender: ethers.Wallet, amt: ethers.BigNumber, bridgeFee: ethers.BigNumber, recipient: string | null) { + let [priorityFee, maxFee] = await getFee(childProvider); + + if (recipient == null) { + return childBridge.connect(sender).withdrawIMX(amt, { + value: amt.add(bridgeFee), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + } else { + return childBridge.connect(sender).withdrawIMXTo(recipient, amt, { + value: amt.add(bridgeFee), + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + } + } + + async function depositETH(sender: ethers.Wallet, amt: ethers.BigNumber, bridgeFee: ethers.BigNumber, recipient: string | null) { + if (recipient == null) { + return rootBridge.connect(sender).depositETH(amt, { + value: amt.add(bridgeFee), + }); + } else { + return rootBridge.connect(sender).depositToETH(recipient, amt, { + value: amt.add(bridgeFee), + }); + } + } + + async function withdrawETH(sender: ethers.Wallet, amt: ethers.BigNumber, bridgeFee: ethers.BigNumber, recipient: string | null) { + let [priorityFee, maxFee] = await getFee(childProvider); + + if (recipient == null) { + return childBridge.connect(sender).withdrawETH(amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }); + } else { + return childBridge.connect(sender).withdrawETHTo(recipient, amt, { + value: bridgeFee, + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + }) + } + } +}) \ No newline at end of file diff --git a/scripts/helpers/helpers.js b/scripts/helpers/helpers.js deleted file mode 100644 index 776846cc..00000000 --- a/scripts/helpers/helpers.js +++ /dev/null @@ -1,66 +0,0 @@ -const { ContractFactory } = require("ethers"); - -exports.delay = (time) => { - return new Promise(resolve => setTimeout(resolve, time)); -} -exports.requireEnv = (envName) => { - let val = process.env[envName]; - if (val == null || val == "") { - throw(envName + " not set!"); - } - if (!envName.includes("SECRET")) { - console.log(envName + ": ", val); - } else { - console.log(envName + " is set."); - } - return val -} -exports.waitForReceipt = async (txHash, provider) => { - let receipt; - while (receipt == null) { - receipt = await provider.getTransactionReceipt(txHash) - await exports.delay(1000); - } - console.log("Receipt: " + JSON.stringify(receipt, null, 2)); - if (receipt.status != 1) { - throw("Fail to execute: " + txHash); - } - console.log("Tx " + txHash + " succeed."); -} -exports.waitForConfirmation = async () => { - if (process.env["SKIP_WAIT_FOR_CONFIRMATION"] == null) { - for (let i = 10; i >= 0; i--) { - console.log(i) - await exports.delay(1000); - } - } -} -exports.getFee = async (wallet) => { - let feeData = await wallet.getFeeData(); - let baseFee = feeData.lastBaseFeePerGas; - let gasPrice = feeData.gasPrice; - let priorityFee = Math.round(gasPrice * 150 / 100); - let maxFee = Math.round(1.13 * baseFee + priorityFee); - return [priorityFee, maxFee]; -} -exports.requireNonEmptyCode = async (provider, addr) => { - if (await provider.getCode(addr) == "0x") { - throw(addr + " has empty code!"); - } - console.log(addr + " has code."); -} -exports.hasDuplicates = (array) => { - return (new Set(array)).size !== array.length; -} -exports.deployChildContract = async (contractObj, adminWallet, ...args) => { - let [priorityFee, maxFee] = await exports.getFee(adminWallet); - let factory = new ContractFactory(contractObj.abi, contractObj.bytecode, adminWallet); - return await factory.deploy(...args, { - maxPriorityFeePerGas: priorityFee, - maxFeePerGas: maxFee, - }); -} -exports.deployRootContract = async (contractObj, adminWallet, ...args) => { - let factory = new ContractFactory(contractObj.abi, contractObj.bytecode, adminWallet); - return await factory.deploy(...args); -} \ No newline at end of file diff --git a/scripts/helpers/helpers.ts b/scripts/helpers/helpers.ts new file mode 100644 index 00000000..18992b13 --- /dev/null +++ b/scripts/helpers/helpers.ts @@ -0,0 +1,230 @@ +import { ContractFactory, providers, ethers } from "ethers"; +import { LedgerSigner } from "./ledger_signer"; +import * as fs from "fs"; +import util from 'util'; +const exec = util.promisify(require('child_process').exec); + +export function delay(time: number) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +export function requireEnv(envName: string) { + let val = process.env[envName]; + if (val == null || val == "") { + throw(envName + " not set!"); + } + if (!envName.includes("SECRET")) { + console.log(envName + ": ", val); + } else { + console.log(envName + " is set."); + } + return val +} + +export async function waitForReceipt(txHash: string, provider: providers.JsonRpcProvider) { + let receipt; + while (receipt == null) { + receipt = await provider.getTransactionReceipt(txHash) + await exports.delay(1000); + } + if (receipt.status != 1) { + throw("Fail to execute: " + txHash); + } + console.log("Tx " + txHash + " succeed."); +} + +export async function waitForConfirmation() { + if (process.env["SKIP_WAIT_FOR_CONFIRMATION"] == null) { + for (let i = 10; i >= 0; i--) { + console.log(i) + await exports.delay(1000); + } + } +} + +export async function getFee(provider: providers.JsonRpcProvider) { + let feeData = await provider.getFeeData(); + let baseFee = feeData.lastBaseFeePerGas; + let gasPrice = feeData.gasPrice; + let priorityFee; + let maxFee; + if (gasPrice && baseFee) { + priorityFee = gasPrice.mul(150).div(100); + maxFee = baseFee.mul(113).div(100).add(priorityFee); + } else { + priorityFee = ethers.utils.parseUnits("110", "gwei"); + maxFee = ethers.utils.parseUnits("120", "gwei"); + } + return [priorityFee, maxFee]; +} + +export async function requireNonEmptyCode(provider: providers.JsonRpcProvider, addr: string) { + if (await provider.getCode(addr) == "0x") { + throw(addr + " has empty code!"); + } + console.log(addr + " has code."); +} + +export function hasDuplicates(array: string[]) { + return (new Set(array)).size !== array.length; +} + +export async function deployChildContract(contract: string, adminWallet: ethers.Wallet | LedgerSigner, reservedNonce: number | null, ...args: any) { + let contractObj = JSON.parse(fs.readFileSync(`../../out/${contract}.sol/${contract}.json`, 'utf8')); + let [priorityFee, maxFee] = await exports.getFee(adminWallet); + let factory = new ContractFactory(contractObj.abi, contractObj.bytecode, adminWallet); + let overrides; + if (reservedNonce != null) { + overrides = { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + nonce: reservedNonce, + } + } else { + overrides = { + maxPriorityFeePerGas: priorityFee, + maxFeePerGas: maxFee, + } + } + return await factory.deploy(...args, overrides); +} + +export async function deployRootContract(contract: string, adminWallet: ethers.Wallet | LedgerSigner, reservedNonce: number | null, ...args: any) { + let contractObj = JSON.parse(fs.readFileSync(`../../out/${contract}.sol/${contract}.json`, 'utf8')); + let factory = new ContractFactory(contractObj.abi, contractObj.bytecode, adminWallet); + if (reservedNonce == null) { + return await factory.deploy(...args); + } else { + return await factory.deploy(...args, { + nonce: reservedNonce, + }) + } +} + +export function getContract(contract: string, contractAddr: string, provider: providers.JsonRpcProvider) { + let contractObj = JSON.parse(fs.readFileSync(`../../out/${contract}.sol/${contract}.json`, 'utf8')); + return new ethers.Contract(contractAddr, contractObj.abi, provider); +} + +export function getChildContracts() { + let childContracts; + if (fs.existsSync(".child.bridge.contracts.json")) { + let data = fs.readFileSync(".child.bridge.contracts.json", 'utf-8'); + childContracts = JSON.parse(data); + } else { + childContracts = { + CHILD_PROXY_ADMIN: "", + CHILD_BRIDGE_IMPL_ADDRESS: "", + CHILD_BRIDGE_PROXY_ADDRESS: "", + CHILD_BRIDGE_ADDRESS: "", + CHILD_ADAPTOR_IMPL_ADDRESS: "", + CHILD_ADAPTOR_PROXY_ADDRESS: "", + CHILD_ADAPTOR_ADDRESS: "", + CHILD_TOKEN_TEMPLATE: "", + WRAPPED_IMX_ADDRESS: "", + CHILD_TEST_CUSTOM_TOKEN: "", + }; + } + return childContracts; +} + +export function saveChildContracts(contractData: any) { + fs.writeFileSync(".child.bridge.contracts.json", JSON.stringify(contractData, null, 2)); +} + +export function getRootContracts() { + let rootContracts; + if (fs.existsSync(".root.bridge.contracts.json")) { + let data = fs.readFileSync(".root.bridge.contracts.json", 'utf-8'); + rootContracts = JSON.parse(data); + } else { + rootContracts = { + ROOT_PROXY_ADMIN: "", + ROOT_BRIDGE_IMPL_ADDRESS: "", + ROOT_BRIDGE_PROXY_ADDRESS: "", + ROOT_BRIDGE_ADDRESS: "", + ROOT_ADAPTOR_IMPL_ADDRESS: "", + ROOT_ADAPTOR_PROXY_ADDRESS: "", + ROOT_ADAPTOR_ADDRESS: "", + ROOT_TOKEN_TEMPLATE: "", + ROOT_TEST_CUSTOM_TOKEN: "", + }; + } + return rootContracts; +} + +export function saveRootContracts(contractData: any) { + fs.writeFileSync(".root.bridge.contracts.json", JSON.stringify(contractData, null, 2)); +} + +export async function waitUntilSucceed(axelarURL: string, txHash: any) { + if (axelarURL == "skip") { + return; + } + console.log("Wait until succeed... tx hash: ", txHash) + let response; + let req = '{"method": "searchGMP", "txHash": "' + txHash + '"}' + while (true) { + response = await fetch(axelarURL, { + method: 'POST', + body: req, + headers: {'Content-Type': 'application/json; charset=UTF-8'} }); + if (!response.ok) {} + if (response.body !== null) { + const asString = new TextDecoder("utf-8").decode(await response.arrayBuffer()); + const asJSON = JSON.parse(asString); + if (asJSON.data[0] == undefined) { + console.log("Waiting for " + txHash + " to become available..."); + } else { + console.log("Current status of " + txHash + ": " + asJSON.data[0].status); + if (asJSON.data[0].status == "executed") { + console.log("Done"); + return; + } + } + } + await delay(60000); + } +} + +export async function verifyChildContract(contract: string, contractAddr: string) { + console.log("Verifying " + contract + " at " + contractAddr + " on child chain..."); + let url = process.env["CHILD_CHAIN_BLOCKSCOUT_API_URL"]; + if (url == null || url == "") { + console.log("CHILD_CHAIN_BLOCKSCOUT_API_URL not set, skip contract verification..."); + return; + } + let cmd = `forge verify-contract --verifier blockscout --verifier-url ${url} ${contractAddr} ${contract}`; + try { + const { stdout, stderr } = await exec(cmd); + if (stderr != "") { + console.log(stderr); + } + console.log(stdout); + } catch (e) { + console.log(e); + } +} + +export async function verifyRootContract(contract: string, contractAddr: string, args: string | null) { + console.log("Verifying " + contract + " at " + contractAddr + " on root chain..."); + let key = process.env["ROOT_CHAIN_ETHERSCAN_API_KEY"]; + if (key == null || key == "") { + console.log("ROOT_CHAIN_ETHERSCAN_API_KEY not set, skip contract verification..."); + return; + } + let chainID = requireEnv("ROOT_CHAIN_ID"); + let cmd = `ETHERSCAN_API_KEY=${key} forge verify-contract ${contractAddr} ${contract} --chain-id ${chainID}`; + if (args != null) { + cmd += ` --constructor-args $(cast abi-encode ${args})` + } + try { + const { stdout, stderr } = await exec(cmd); + if (stderr != "") { + console.log(stderr); + } + console.log(stdout); + } catch (e) { + console.log(e); + } +} \ No newline at end of file diff --git a/scripts/helpers/ledger_signer.ts b/scripts/helpers/ledger_signer.ts new file mode 100644 index 00000000..58b3e1c3 --- /dev/null +++ b/scripts/helpers/ledger_signer.ts @@ -0,0 +1,149 @@ +// Copied from https://github.com/immutable/imx-engine/blob/77b8a62e6ac0baf033519e0ed533316eead3bc23/services/order-book-mr/e2e/scripts/ledger-signer.ts +import { ethers } from "ethers"; +import Eth from "@ledgerhq/hw-app-eth"; +import TransportNodeHid from "@ledgerhq/hw-transport-node-hid"; +import { + defineReadOnly, + hexlify, + resolveProperties, + serializeTransaction, + toUtf8Bytes, + UnsignedTransaction, +} from "ethers/lib/utils"; +import { toBuffer, toRpcSig } from "@nomicfoundation/ethereumjs-util"; +import ledgerService from "@ledgerhq/hw-app-eth/lib/services/ledger"; + +const DEFAULT_LEDGER_PATH = "m/44'/60'/0'/0/0"; + +function toHex(value: string | Buffer): string { + const stringValue = typeof value === "string" ? value : value.toString("hex"); + return stringValue.startsWith("0x") ? stringValue : `0x${stringValue}`; +} + +// Simple LedgerSigner that wraps @ledgerhq/hw-transport-node-hid to deploy +// contracts using hardware wallet. +export class LedgerSigner extends ethers.Signer { + readonly path: string; + readonly _eth: Promise | undefined; + + constructor( + provider?: ethers.providers.Provider, + path: string = DEFAULT_LEDGER_PATH + ) { + super(); + this.path = path || DEFAULT_LEDGER_PATH; + + defineReadOnly(this, "path", path); + defineReadOnly(this, "provider", provider || undefined); + defineReadOnly( + this, + "_eth", + TransportNodeHid.create().then(async (transport) => { + try { + const eth = new Eth(transport); + await eth.getAppConfiguration(); + return eth; + } catch (error) { + throw "LedgerSigner: unable to initialize TransportNodeHid: " + error; + } + }) + ); + } + + private async _withConfirmation any>( + func: T + ): Promise> { + try { + const result = await func(); + + return result; + } catch (error) { + throw new Error("LedgerSigner: confirmation_failure: " + error); + } + } + + public async getAddress(): Promise { + const eth = await this._eth; + + const MAX_RETRY_COUNT = 50; + const WAIT_INTERVAL = 100; + + for (let i = 0; i < MAX_RETRY_COUNT; i++) { + try { + const account = await eth!.getAddress(this.path); + return ethers.utils.getAddress(account.address); + } catch (error) { + if ((error as any).id !== "TransportLocked") { + throw error; + } + } + await new Promise((resolve) => setTimeout(resolve, WAIT_INTERVAL)); + } + + throw new Error("LedgerSigner: getAddress timed out"); + } + + public async signMessage( + message: ethers.utils.Bytes | string + ): Promise { + const resolvedMessage = + typeof message === "string" ? toUtf8Bytes(message) : message; + + const eth = await this._eth; + const signature = await this._withConfirmation(() => + eth!.signPersonalMessage(this.path, hexlify(resolvedMessage)) + ); + + return toRpcSig( + BigInt(signature.v - 27), + toBuffer(toHex(signature.r)), + toBuffer(toHex(signature.s)) + ); + } + + async signTransaction( + transaction: ethers.providers.TransactionRequest + ): Promise { + const txRequest = await resolveProperties(transaction); + + const baseTx: UnsignedTransaction = { + type: txRequest.type, + data: txRequest.data, + chainId: txRequest.chainId, + gasLimit: txRequest.gasLimit, + gasPrice: txRequest.gasPrice, + nonce: Number(txRequest.nonce), + value: txRequest.value, + to: txRequest.to, + }; + + // Type-2 transaction, with tip + if (txRequest.type === 2) { + baseTx.maxFeePerGas = txRequest.maxFeePerGas; + baseTx.maxPriorityFeePerGas = txRequest.maxPriorityFeePerGas; + } + + const txToSign = serializeTransaction(baseTx).substring(2); + + const resolution = await ledgerService.resolveTransaction(txToSign, {}, {}); + + const eth = await this._eth; + const signature = await this._withConfirmation(() => + eth!.signTransaction(this.path, txToSign, resolution) + ); + + return serializeTransaction(baseTx, { + v: Number(signature.v), + r: toHex(signature.r), + s: toHex(signature.s), + }); + } + + connect(provider: ethers.providers.Provider): ethers.Signer { + return new LedgerSigner(provider, this.path); + } + + public async close() { + (await this._eth)?.transport.close(); + } +} \ No newline at end of file diff --git a/scripts/helpers/retry.ts b/scripts/helpers/retry.ts new file mode 100644 index 00000000..9e0d4cb5 --- /dev/null +++ b/scripts/helpers/retry.ts @@ -0,0 +1,39 @@ +import { providers, utils } from "ethers"; + +const MAX_ATTEMPT = 20; + +export class RetryProvider extends providers.JsonRpcProvider { + + constructor( + url?: utils.ConnectionInfo | string, + network?: providers.Networkish + ) { + super(url, network); + } + + public perform(method: string, params: any) { + let attempts = 0; + return utils.poll(() => { + if (attempts != 0) { + console.log("Retry RPC Request: " + attempts); + } + attempts++; + return super.perform(method, params) + .then(result => { + return result; + }, (error: any) => { + if (error.statusCode !== 429) { + return Promise.reject(error); + } else { + return Promise.resolve(undefined); + } + }) + .catch(error => { + console.log(error); + return Promise.resolve(undefined); + }) + }, { + retryLimit: MAX_ATTEMPT, + }); + } +} diff --git a/scripts/localdev/.env.local b/scripts/localdev/.env.local index e4572792..da8b99ad 100644 --- a/scripts/localdev/.env.local +++ b/scripts/localdev/.env.local @@ -1,74 +1,56 @@ -# Set prior to 1_deployer_funding.js +# Set prior to 0_pre_validation.js +# Name of the child chain MUST match Axelar's definition. CHILD_CHAIN_NAME="Immutable zkEVM E2E" +# The RPC URL of child chain. CHILD_RPC_URL=http://127.0.0.1:8501 +# The chain ID of the child chain. CHILD_CHAIN_ID=2501 +# Name of the root chain MUST match Axelar's definition. ROOT_CHAIN_NAME="Ethereum E2E" +# The RPC URL of root chain. ROOT_RPC_URL=http://127.0.0.1:8500 +# The chain ID of the root chain. ROOT_CHAIN_ID=2500 -## The admin EOA address on the child chain. -CHILD_ADMIN_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -## The private key for the admin EOA or "ledger" if using hardware wallet. -CHILD_ADMIN_EOA_SECRET=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -## The deployer address on child chain. -CHILD_DEPLOYER_ADDR=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 -## The private key for the deployer on child chain or "ledger" if using hardware wallet. -CHILD_DEPLOYER_SECRET=59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d -## The amount of fund deployer required on L2, unit is in IMX or 10^18 Wei. -CHILD_DEPLOYER_FUND=500 -## The deployer address on root chain. -ROOT_DEPLOYER_ADDR=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 -## The private key for the deployer on root chain or "ledger" if using hardware wallet. -ROOT_DEPLOYER_SECRET=59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d -## The private key for rate admin or "ledger" if using hardware wallet. -ROOT_BRIDGE_RATE_ADMIN_SECRET=8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba +## The deployer address on child & root chains. +DEPLOYER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +## The private key for the deployer on child & root chains or "ledger" if using hardware wallet. +DEPLOYER_SECRET=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +## The ledger index for the deployer on child & root chains, required if using ledger. +DEPLOYER_LEDGER_INDEX= +## The nonce reserved deployer address on child & root chains. +NONCE_RESERVED_DEPLOYER_ADDR=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +## The nonce reserved deployer, or "ledger" if using hardware wallet. +NONCE_RESERVED_DEPLOYER_SECRET=59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d +## The ledger index for the nonce reserved deployer. +NONCE_RESERVED_DEPLOYER_INDEX= +## The reserved nonce for token template deployment. +NONCE_RESERVED=0 ## The IMX token address on root chain. ROOT_IMX_ADDR=0x73511669fd4dE447feD18BB79bAFeAC93aB7F31f ## The Wrapped ETH token address on the root chain. ROOT_WETH_ADDR=0xB581C9264f59BF0289fA76D61B2D0746dCE3C30D -## The Axelar address for receive initial funding on the child chain. +## The Axelar address to receive initial funding on the child chain. AXELAR_EOA=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC +## The passport nonce reserver +PASSPORT_NONCE_RESERVER_ADDR=0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 ## The amount of fund Axelar requested, unit is in IMX or 10^18 Wei. AXELAR_FUND=500 +## The amount of fund deployer to be left with after bootstrapping on L2, unit is in IMX or 10^18 Wei. +CHILD_DEPLOYER_FUND=200 +## The amount of fund nonce reserved deployer required on L2, unit is in IMX or 10^18 Wei. +CHILD_NONCE_RESERVED_DEPLOYER_FUND=200 +## The amount of fund passport reserver required on L2, unit is in IMX or 10^18 Wei. +PASSPORT_NONCE_RESERVER_FUND=100 ## The maximum amount of IMX that can be deposited to L2, unit is in IMX or 10^18 Wei. -IMX_DEPOSIT_LIMIT=200000000 -## The address to perform child bridge upgrade. -CHILD_PROXY_ADMIN=0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 -## The address to be assigned with DEFAULT_ADMIN_ROLE in child bridge. -CHILD_BRIDGE_DEFAULT_ADMIN=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with PAUSER_ROLE in child bridge. -CHILD_BRIDGE_PAUSER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with UNPAUSER_ROLE in child bridge. -CHILD_BRIDGE_UNPAUSER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with ADAPTOR_MANAGER_ROLE in child bridge. -CHILD_BRIDGE_ADAPTOR_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with DEFAULT_ADMIN_ROLE in child adaptor. -CHILD_ADAPTOR_DEFAULT_ADMIN=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with BRIDGE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_BRIDGE_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_GAS_SERVICE_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with TARGET_MANAGER_ROLE in child adaptor. -CHILD_ADAPTOR_TARGET_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to perform root adaptor upgrade. -ROOT_PROXY_ADMIN=0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 -## The address to be assigned with DEFAULT_ADMIN_ROLE in root bridge. -ROOT_BRIDGE_DEFAULT_ADMIN=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with PAUSER_ROLE in root bridge. -ROOT_BRIDGE_PAUSER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with UNPAUSER_ROLE in root bridge. -ROOT_BRIDGE_UNPAUSER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with VARIABLE_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_VARIABLE_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with ADAPTOR_MANAGER_ROLE in root bridge. -ROOT_BRIDGE_ADAPTOR_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with DEFAULT_ADMIN_ROLE in root adaptor. -ROOT_ADAPTOR_DEFAULT_ADMIN=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with BRIDGE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_BRIDGE_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with GAS_SERVICE_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_GAS_SERVICE_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc -## The address to be assigned with TARGET_MANAGER_ROLE in root adaptor. -ROOT_ADAPTOR_TARGET_MANAGER=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc +IMX_DEPOSIT_LIMIT=100000000 +## The privileged transaction multisig address on the root chain. +ROOT_PRIVILEGED_MULTISIG_ADDR=0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 +# The break glass signer address on the root chain. +ROOT_BREAKGLASS_ADDR=0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f +## The privileged transaction Multisig address on the child chain. +CHILD_PRIVILEGED_MULTISIG_ADDR=0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 +# The break glass signer address on the child chain. +CHILD_BREAKGLASS_ADDR=0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f ## The capacity of the rate limit policy of IMX token, unit is in 10^18. RATE_LIMIT_IMX_CAPACITY=15516 ## The refill rate of the rate limit policy of IMX token, unit is in 10^18. @@ -128,4 +110,6 @@ TEST_ACCOUNT_SECRET=92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1 ROOT_EOA_SECRET=df57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e AXELAR_ROOT_EOA_SECRET=5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a AXELAR_CHILD_EOA_SECRET=5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a -AXELAR_DEPLOYER_SECRET=7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 \ No newline at end of file +AXELAR_DEPLOYER_SECRET=7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 +BREAKGLASS_EOA_SECRET=dbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 +PRIVILEGED_EOA_SECRET=4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 \ No newline at end of file diff --git a/scripts/localdev/README.md b/scripts/localdev/README.md index fb294cab..5d47f518 100644 --- a/scripts/localdev/README.md +++ b/scripts/localdev/README.md @@ -31,5 +31,5 @@ The addresses of deployed contracts will be saved in: To run end to end tests against local development network: ``` -npx mocha --require mocha-suppress-logs ../e2e/ +AXELAR_API_URL=skip npx mocha --require mocha-suppress-logs ../e2e/e2e.ts ``` \ No newline at end of file diff --git a/scripts/localdev/axelar_setup.js b/scripts/localdev/axelar_setup.ts similarity index 77% rename from scripts/localdev/axelar_setup.js rename to scripts/localdev/axelar_setup.ts index 81a467af..4450f3bd 100644 --- a/scripts/localdev/axelar_setup.js +++ b/scripts/localdev/axelar_setup.ts @@ -1,26 +1,27 @@ -'use strict'; -const { Network, networks, EvmRelayer, relay } = require('@axelar-network/axelar-local-dev'); -const helper = require("../helpers/helpers.js"); -const { ethers } = require("ethers"); -const fs = require('fs'); -require('dotenv').config(); +import * as dotenv from "dotenv"; +dotenv.config(); +import { Network, networks, EvmRelayer, relay } from '@axelar-network/axelar-local-dev'; +import { requireEnv, waitForReceipt } from "../helpers/helpers"; +import { ethers } from "ethers"; +import * as fs from "fs"; +import { RetryProvider } from "../helpers/retry"; let relaying = false; const defaultEvmRelayer = new EvmRelayer(); async function main() { - let rootChainName = helper.requireEnv("ROOT_CHAIN_NAME"); - let rootRPCURL = helper.requireEnv("ROOT_RPC_URL"); - let rootChainID = helper.requireEnv("ROOT_CHAIN_ID"); - let childChainName = helper.requireEnv("CHILD_CHAIN_NAME"); - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let axelarRootEOAKey = helper.requireEnv("AXELAR_ROOT_EOA_SECRET"); - let axelarChildEOAKey = helper.requireEnv("AXELAR_CHILD_EOA_SECRET"); - let axelarDeployerKey = helper.requireEnv("AXELAR_DEPLOYER_SECRET"); + let rootChainName = requireEnv("ROOT_CHAIN_NAME"); + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let childChainName = requireEnv("CHILD_CHAIN_NAME"); + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let axelarRootEOAKey = requireEnv("AXELAR_ROOT_EOA_SECRET"); + let axelarChildEOAKey = requireEnv("AXELAR_CHILD_EOA_SECRET"); + let axelarDeployerKey = requireEnv("AXELAR_DEPLOYER_SECRET"); // Create root chain. - let rootProvider = new ethers.providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); + let rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); let rootChain = new Network(); rootChain.name = rootChainName; rootChain.chainId = Number(rootChainID); @@ -45,7 +46,7 @@ async function main() { rootChain.lastExpressedBlock = rootChain.lastRelayedBlock; // Create child chain. - let childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); + let childProvider = new RetryProvider(childRPCURL, Number(childChainID)); let childChain = new Network(); childChain.name = childChainName; childChain.chainId = Number(childChainID); @@ -75,23 +76,23 @@ async function main() { to: childChain.ownerWallet.address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, childProvider); + await waitForReceipt(resp.hash, childProvider); resp = await axelarChildEOA.sendTransaction({ to: childChain.operatorWallet.address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, childProvider); + await waitForReceipt(resp.hash, childProvider); resp = await axelarChildEOA.sendTransaction({ to: childChain.relayerWallet.address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, childProvider); + await waitForReceipt(resp.hash, childProvider); for (let i = 0; i < 10; i++) { resp = await axelarChildEOA.sendTransaction({ to: childChain.adminWallets[i].address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, childProvider); + await waitForReceipt(resp.hash, childProvider); } // Deploy child contracts. await childChain.deployConstAddressDeployer(); @@ -106,23 +107,23 @@ async function main() { to: rootChain.ownerWallet.address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, rootProvider); + await waitForReceipt(resp.hash, rootProvider); resp = await axelarRootEOA.sendTransaction({ to: rootChain.operatorWallet.address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, rootProvider); + await waitForReceipt(resp.hash, rootProvider); resp = await axelarRootEOA.sendTransaction({ to: rootChain.relayerWallet.address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, rootProvider); + await waitForReceipt(resp.hash, rootProvider); for (let i = 0; i < 10; i++) { resp = await axelarRootEOA.sendTransaction({ to: rootChain.adminWallets[i].address, value: ethers.utils.parseEther("35.0"), }) - await helper.waitForReceipt(resp.hash, rootProvider); + await waitForReceipt(resp.hash, rootProvider); } // Deploy root contracts. await rootChain.deployConstAddressDeployer(); diff --git a/scripts/localdev/childchain.config.js b/scripts/localdev/childchain.config.ts similarity index 56% rename from scripts/localdev/childchain.config.js rename to scripts/localdev/childchain.config.ts index 9d968f23..c91065e3 100644 --- a/scripts/localdev/childchain.config.js +++ b/scripts/localdev/childchain.config.ts @@ -1,9 +1,10 @@ -/** @type import('hardhat/config').HardhatUserConfig */ -require("@nomicfoundation/hardhat-toolbox"); +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox"; -module.exports = { +const config: HardhatUserConfig = { networks: { hardhat: { + hardfork: "grayGlacier", mining: { auto: false, interval: 200 @@ -17,3 +18,4 @@ module.exports = { }, solidity: "0.8.19", }; +export default config; \ No newline at end of file diff --git a/scripts/localdev/childchain_setup.js b/scripts/localdev/childchain_setup.js deleted file mode 100644 index e1f74557..00000000 --- a/scripts/localdev/childchain_setup.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; -const { ethers: hardhat } = require("hardhat"); -const { ethers } = require("ethers"); -const helper = require("../helpers/helpers.js"); -require('dotenv').config(); - -async function main() { - let childRPCURL = helper.requireEnv("CHILD_RPC_URL"); - let childChainID = helper.requireEnv("CHILD_CHAIN_ID"); - let childEOAKey = helper.requireEnv("CHILD_ADMIN_EOA_SECRET"); - - // Get child provider. - let childProvider = new ethers.providers.JsonRpcProvider(childRPCURL, Number(childChainID)); - - // Get admin EOA on the child chain. - let childEOA = new ethers.Wallet(childEOAKey); - - // Give admin EOA account 2B IMX. - await hardhat.provider.send("hardhat_setBalance", [ - childEOA.address, - "0x6765c793fa10079d0000000", - ]); - - console.log("Child admin EOA now has " + ethers.utils.formatEther(await childProvider.getBalance(childEOA.address)) + " IMX."); - console.log("Finished setting up on child chain.") -} -main(); \ No newline at end of file diff --git a/scripts/localdev/childchain_setup.ts b/scripts/localdev/childchain_setup.ts new file mode 100644 index 00000000..6a0db0a8 --- /dev/null +++ b/scripts/localdev/childchain_setup.ts @@ -0,0 +1,25 @@ +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers as hardhat } from "hardhat"; +import { ethers } from "ethers"; +import { requireEnv } from "../helpers/helpers"; +import { RetryProvider } from "../helpers/retry"; + +async function main() { + let childRPCURL = requireEnv("CHILD_RPC_URL"); + let childChainID = requireEnv("CHILD_CHAIN_ID"); + let deployerAddr = requireEnv("DEPLOYER_ADDR"); + + // Get child provider. + let childProvider = new RetryProvider(childRPCURL, Number(childChainID)); + + // Give admin EOA account 2B IMX. + await hardhat.provider.send("hardhat_setBalance", [ + deployerAddr, + "0x6765c793fa10079d0000000", + ]); + + console.log("Child admin EOA now has " + ethers.utils.formatEther(await childProvider.getBalance(deployerAddr)) + " IMX."); + console.log("Finished setting up on child chain.") +} +main(); \ No newline at end of file diff --git a/scripts/localdev/ci.sh b/scripts/localdev/ci.sh index 003ea77a..b510e099 100755 --- a/scripts/localdev/ci.sh +++ b/scripts/localdev/ci.sh @@ -5,7 +5,7 @@ counter=1 while [ $counter -le 300 ] do echo "Waiting for chain and axelar setup... ${counter}" - SKIP_WAIT_FOR_CONFIRMATION=true SKIP_MULTISIG_CHECK=true node ../bootstrap/2_deployment_validation.js > /dev/null 2>&1 + SKIP_WAIT_FOR_CONFIRMATION=true SKIP_MULTISIG_CHECK=true npx ts-node ../bootstrap/2_deployment_validation.ts > /dev/null 2>&1 if [ $? -ne 0 ]; then sleep 1 ((counter++)) diff --git a/scripts/localdev/deploy.sh b/scripts/localdev/deploy.sh index 3fef152b..c5e2b24f 100755 --- a/scripts/localdev/deploy.sh +++ b/scripts/localdev/deploy.sh @@ -3,22 +3,25 @@ set -ex set -o pipefail # Verify deployment -SKIP_WAIT_FOR_CONFIRMATION=true SKIP_MULTISIG_CHECK=true node ../bootstrap/2_deployment_validation.js 2>&1 | tee -a bootstrap.out +SKIP_WAIT_FOR_CONFIRMATION=true SKIP_MULTISIG_CHECK=true npx ts-node ../bootstrap/2_deployment_validation.ts 2>&1 | tee -a bootstrap.out # Deploy child contracts -SKIP_WAIT_FOR_CONFIRMATION=true node ../bootstrap/3_child_deployment.js 2>&1 | tee -a bootstrap.out +SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/3_child_deployment.ts 2>&1 | tee -a bootstrap.out # Deploy root contracts -SKIP_WAIT_FOR_CONFIRMATION=true node ../bootstrap/4_root_deployment.js 2>&1 | tee -a bootstrap.out +SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/4_root_deployment.ts 2>&1 | tee -a bootstrap.out # Initialise child contracts -SKIP_WAIT_FOR_CONFIRMATION=true node ../bootstrap/5_child_initialisation.js 2>&1 | tee -a bootstrap.out +SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/5_child_initialisation.ts 2>&1 | tee -a bootstrap.out # IMX Burning -SKIP_WAIT_FOR_CONFIRMATION=true node ../bootstrap/6_imx_burning.js 2>&1 | tee -a bootstrap.out +SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/6_imx_burning.ts 2>&1 | tee -a bootstrap.out # IMX Rebalancing -SKIP_WAIT_FOR_CONFIRMATION=true node ../bootstrap/7_imx_rebalancing.js 2>&1 | tee -a bootstrap.out +SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/7_imx_rebalancing.ts 2>&1 | tee -a bootstrap.out # Initialise root contracts -SKIP_WAIT_FOR_CONFIRMATION=true node ../bootstrap/8_root_initialisation.js 2>&1 | tee -a bootstrap.out \ No newline at end of file +SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/8_root_initialisation.ts 2>&1 | tee -a bootstrap.out + +# Prepare for test +SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/9_test_preparation.ts 2>&1 | tee -a bootstrap.out \ No newline at end of file diff --git a/scripts/localdev/rootchain.config.js b/scripts/localdev/rootchain.config.ts similarity index 55% rename from scripts/localdev/rootchain.config.js rename to scripts/localdev/rootchain.config.ts index 1177b63f..6ec638b6 100644 --- a/scripts/localdev/rootchain.config.js +++ b/scripts/localdev/rootchain.config.ts @@ -1,9 +1,10 @@ -/** @type import('hardhat/config').HardhatUserConfig */ -require("@nomicfoundation/hardhat-toolbox"); +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox"; -module.exports = { +const config: HardhatUserConfig = { networks: { hardhat: { + hardfork: "shanghai", mining: { auto: false, interval: 1200 @@ -16,4 +17,5 @@ module.exports = { } }, solidity: "0.8.19", -}; \ No newline at end of file +}; +export default config; \ No newline at end of file diff --git a/scripts/localdev/rootchain_setup.js b/scripts/localdev/rootchain_setup.js deleted file mode 100644 index cc7e5b35..00000000 --- a/scripts/localdev/rootchain_setup.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; -const { ethers: hardhat } = require("hardhat"); -const { ethers, ContractFactory } = require("ethers"); -const helper = require("../helpers/helpers.js"); -const fs = require('fs'); -require('dotenv').config(); - -async function main() { - let rootRPCURL = helper.requireEnv("ROOT_RPC_URL"); - let rootChainID = helper.requireEnv("ROOT_CHAIN_ID"); - let rootAdminKey = helper.requireEnv("ROOT_EOA_SECRET"); - let rootDeployerKey = helper.requireEnv("ROOT_DEPLOYER_SECRET"); - let axelarDeployerKey = helper.requireEnv("AXELAR_ROOT_EOA_SECRET"); - let rootTestKey = helper.requireEnv("TEST_ACCOUNT_SECRET"); - let rootRateAdminKey = helper.requireEnv("ROOT_BRIDGE_RATE_ADMIN_SECRET"); - - // Get root provider. - let rootProvider = new ethers.providers.JsonRpcProvider(rootRPCURL, Number(rootChainID)); - - // Get deployer wallet on the root chain. - let rootDeployer = new ethers.Wallet(rootDeployerKey, rootProvider); - - // Get axelar wallet on the root chain. - let axelarDeployer = new ethers.Wallet(axelarDeployerKey, rootProvider); - - // Get test wwallet on the root chain. - let testWallet = new ethers.Wallet(rootTestKey, rootProvider); - - // Get rate admin wallet on the root chain. - let rateAdminWallet = new ethers.Wallet(rootRateAdminKey, rootProvider); - - // Get root admin eoa wallet. - let admin = new ethers.Wallet(rootAdminKey, rootProvider); - - // Give admin account 10000 ETH. - await hardhat.provider.send("hardhat_setBalance", [ - admin.address, - "0x21E19E0C9BAB2400000", - ]); - - // Deploy IMX contract - let IMXObj = JSON.parse(fs.readFileSync('../../out/ERC20PresetMinterPauser.sol/ERC20PresetMinterPauser.json', 'utf8')); - console.log("Deploy IMX contract on root chain..."); - - let IMXFactory = new ContractFactory(IMXObj.abi, IMXObj.bytecode, admin); - let IMX = await IMXFactory.deploy("IMX Token", "IMX"); - let txn = IMX.deployTransaction; - await helper.waitForReceipt(txn.hash, rootProvider); - console.log("IMX deployed at: " + IMX.address); - - // Deploy WETH contract - let WETHObj = JSON.parse(fs.readFileSync('../../out/WETH.sol/WETH.json', 'utf8')) - console.log("Deploy WETH contract on root chain..."); - let WETHFactory = new ContractFactory(WETHObj.abi, WETHObj.bytecode, admin); - let WETH = await WETHFactory.deploy(); - txn = WETH.deployTransaction; - await helper.waitForReceipt(txn.hash, rootProvider); - console.log("WETH deployed at: " + WETH.address); - - // Mint 1100 IMX to root deployer - let resp = await IMX.connect(admin).mint(rootDeployer.address, ethers.utils.parseEther("1100.0")); - await helper.waitForReceipt(resp.hash, rootProvider); - - // Transfer 1000 IMX to test wallet - resp = await IMX.connect(admin).mint(testWallet.address, ethers.utils.parseEther("1000.0")) - await helper.waitForReceipt(resp.hash, rootProvider); - - // Transfer 0.1 ETH to root deployer - resp = await admin.sendTransaction({ - to: rootDeployer.address, - value: ethers.utils.parseEther("0.1"), - }) - await helper.waitForReceipt(resp.hash, rootProvider); - - // Transfer 500 ETH to axelar deployer - resp = await admin.sendTransaction({ - to: axelarDeployer.address, - value: ethers.utils.parseEther("500.0"), - }) - - // Transfer 10 ETH to test wallet - resp = await admin.sendTransaction({ - to: testWallet.address, - value: ethers.utils.parseEther("10.0"), - }) - await helper.waitForReceipt(resp.hash, rootProvider); - - // Transfer 0.1 ETH to rate admin - resp = await admin.sendTransaction({ - to: rateAdminWallet.address, - value: ethers.utils.parseEther("10.0"), - }) - await helper.waitForReceipt(resp.hash, rootProvider); - - console.log("Root deployer now has " + ethers.utils.formatEther(await IMX.balanceOf(rootDeployer.address)) + " IMX."); - console.log("Root deployer now has " + ethers.utils.formatEther(await rootProvider.getBalance(rootDeployer.address)) + " ETH."); - console.log("Root axelar now has " + ethers.utils.formatEther(await rootProvider.getBalance(axelarDeployer.address)) + " ETH."); - - console.log("Finished setting up on root chain."); - let contractData = { - IMX_ROOT_ADDR: IMX.address, - WETH_ROOT_ADDR: WETH.address, - }; - fs.writeFileSync(".root.contracts.json", JSON.stringify(contractData, null, 2)); -} -main(); \ No newline at end of file diff --git a/scripts/localdev/rootchain_setup.ts b/scripts/localdev/rootchain_setup.ts new file mode 100644 index 00000000..523b528e --- /dev/null +++ b/scripts/localdev/rootchain_setup.ts @@ -0,0 +1,93 @@ +import * as dotenv from "dotenv"; +dotenv.config(); +import { ethers as hardhat } from "hardhat"; +import { ethers } from "ethers"; +import { requireEnv, deployRootContract, waitForReceipt, saveRootContracts } from "../helpers/helpers"; +import * as fs from "fs"; +import { RetryProvider } from "../helpers/retry"; + +async function main() { + let rootRPCURL = requireEnv("ROOT_RPC_URL"); + let rootChainID = requireEnv("ROOT_CHAIN_ID"); + let rootAdminKey = requireEnv("ROOT_EOA_SECRET"); + let deployerAddr = requireEnv("DEPLOYER_ADDR"); + let reservedAddr = requireEnv("NONCE_RESERVED_DEPLOYER_ADDR"); + let axelarEOA = requireEnv("AXELAR_EOA"); + let rootTestKey = requireEnv("TEST_ACCOUNT_SECRET"); + let rootBreakGlassAddr = requireEnv("ROOT_BREAKGLASS_ADDR"); + let rootPrivilegedAddr = requireEnv("ROOT_PRIVILEGED_MULTISIG_ADDR"); + + // Get root provider. + let rootProvider = new RetryProvider(rootRPCURL, Number(rootChainID)); + + // Get test wwallet on the root chain. + let testWallet = new ethers.Wallet(rootTestKey, rootProvider); + + // Get root admin eoa wallet. + let admin = new ethers.Wallet(rootAdminKey, rootProvider); + + // Give admin account 10000 ETH. + await hardhat.provider.send("hardhat_setBalance", [ + admin.address, + "0x21E19E0C9BAB2400000", + ]); + + // Deploy IMX contract + console.log("Deploy IMX contract on root chain..."); + let IMX = await deployRootContract("ERC20PresetMinterPauser", admin, null, "IMX Token", "IMX"); + await waitForReceipt(IMX.deployTransaction.hash, rootProvider); + console.log("IMX deployed at: " + IMX.address); + + // Deploy WETH contract + console.log("Deploy WETH contract on root chain..."); + let WETH = await deployRootContract("WETH", admin, null); + await waitForReceipt(WETH.deployTransaction.hash, rootProvider); + console.log("WETH deployed at: " + WETH.address); + + // Mint 1110 IMX to root deployer + let resp = await IMX.connect(admin).mint(deployerAddr, ethers.utils.parseEther("1110.0")); + await waitForReceipt(resp.hash, rootProvider); + + // Transfer 1000000000 IMX to test wallet + resp = await IMX.connect(admin).mint(testWallet.address, ethers.utils.parseEther("1000000000.0")) + await waitForReceipt(resp.hash, rootProvider); + + // Transfer 0.1 ETH to root deployer + resp = await admin.sendTransaction({ + to: deployerAddr, + value: ethers.utils.parseEther("0.1"), + }) + await waitForReceipt(resp.hash, rootProvider); + + // Transfer 0.1 ETH to nonce reserved root deployer + resp = await admin.sendTransaction({ + to: reservedAddr, + value: ethers.utils.parseEther("0.1"), + }) + await waitForReceipt(resp.hash, rootProvider); + + // Transfer 500 ETH to axelar deployer + resp = await admin.sendTransaction({ + to: axelarEOA, + value: ethers.utils.parseEther("500.0"), + }) + + // Transfer 1000 ETH to test wallet + resp = await admin.sendTransaction({ + to: testWallet.address, + value: ethers.utils.parseEther("1000.0"), + }) + await waitForReceipt(resp.hash, rootProvider); + + console.log("Root deployer now has " + ethers.utils.formatEther(await IMX.balanceOf(deployerAddr)) + " IMX."); + console.log("Root deployer now has " + ethers.utils.formatEther(await rootProvider.getBalance(deployerAddr)) + " ETH."); + console.log("Root axelar now has " + ethers.utils.formatEther(await rootProvider.getBalance(axelarEOA)) + " ETH."); + + console.log("Finished setting up on root chain."); + let contractData = { + IMX_ROOT_ADDR: IMX.address, + WETH_ROOT_ADDR: WETH.address, + }; + fs.writeFileSync(".root.contracts.json", JSON.stringify(contractData, null, 2)); +} +main(); \ No newline at end of file diff --git a/scripts/localdev/start.sh b/scripts/localdev/start.sh index 47f7b972..bad47111 100755 --- a/scripts/localdev/start.sh +++ b/scripts/localdev/start.sh @@ -8,8 +8,8 @@ set -o pipefail cp .env.local .env # Start root & child chain. -npx hardhat node --config ./rootchain.config.js --port 8500 > /dev/null 2>&1 & -npx hardhat node --config ./childchain.config.js --port 8501 > /dev/null 2>&1 & +npx hardhat node --config ./rootchain.config.ts --port 8500 > /dev/null 2>&1 & +npx hardhat node --config ./childchain.config.ts --port 8501 > /dev/null 2>&1 & sleep 10 # trap ctrl-c and call ctrl_c() @@ -20,18 +20,21 @@ function ctrl_c() { } # Setup root & child chain. -npx hardhat run ./rootchain_setup.js --config ./rootchain.config.js --network localhost -npx hardhat run ./childchain_setup.js --config ./childchain.config.js --network localhost +npx hardhat run ./rootchain_setup.ts --config ./rootchain.config.ts --network localhost +npx hardhat run ./childchain_setup.ts --config ./childchain.config.ts --network localhost echo "Successfully setup root chain and child chain..." if [ -z ${LOCAL_CHAIN_ONLY+x} ]; then + # Pre validation + SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/0_pre_validation.ts 2>&1 | tee bootstrap.out + # Fund accounts - SKIP_WAIT_FOR_CONFIRMATION=true node ../bootstrap/1_deployer_funding.js 2>&1 | tee bootstrap.out - echo "Successfully run 1_deployer_funding.js..." + SKIP_WAIT_FOR_CONFIRMATION=true npx ts-node ../bootstrap/1_deployer_funding.ts 2>&1 | tee -a bootstrap.out + echo "Successfully run 1_deployer_funding.ts..." # Setup axelar - node axelar_setup.js + npx ts-node axelar_setup.ts ./stop.sh fi \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..0b9e4592 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "outDir": "dist", + "declaration": true, + "resolveJsonModule": true + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 130cde6f..dd5c0ce9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -336,7 +336,7 @@ ethereum-cryptography "^2.0.0" micro-ftch "^0.3.1" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -573,7 +573,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.5.0", "@ethersproject/rlp@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== @@ -713,6 +713,15 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@ledgerhq/cryptoassets@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/cryptoassets/-/cryptoassets-11.2.0.tgz#5594e262fc0aab6e02c1c2bdd950c019712fdaf2" + integrity sha512-O5fVIxzlwyR3YNEJJKUcGNyZW5Nf2lJtm0CPDWIfPaKERwvLPLfuJ5yUSHYBqpvYMGCCFldykiPdZ9XS3+fRaA== + dependencies: + axios "^1.6.0" + bs58check "^2.1.2" + invariant "2" + "@ledgerhq/cryptoassets@^5.27.2": version "5.53.0" resolved "https://registry.yarnpkg.com/@ledgerhq/cryptoassets/-/cryptoassets-5.53.0.tgz#11dcc93211960c6fd6620392e4dd91896aaabe58" @@ -730,11 +739,50 @@ rxjs "6" semver "^7.3.5" +"@ledgerhq/devices@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.1.0.tgz#39b12feabe1c7a99b86667bedf2eafbd125cf217" + integrity sha512-Vsdv84Nwzee0qhObdwVzhkxW1+h2cFoD1AWuU8N1V/2OJKiVS35A1qloSCF0oHapg+KTJvim8tr5rRvlkCYyzQ== + dependencies: + "@ledgerhq/errors" "^6.16.0" + "@ledgerhq/logs" "^6.12.0" + rxjs "^7.8.1" + semver "^7.3.5" + +"@ledgerhq/domain-service@^1.1.15": + version "1.1.15" + resolved "https://registry.yarnpkg.com/@ledgerhq/domain-service/-/domain-service-1.1.15.tgz#fb7664c61c83c0230f8aec35861ac81bdb605c0b" + integrity sha512-1X4MvNhVDTXCfOQckaUHsq/Qzn8xhFMcHjnLKOuCR5zNB8hYuTyg9e7JXURZ8W7/Qcn41rvIPxXBHwvMjWQBMA== + dependencies: + "@ledgerhq/errors" "^6.16.0" + "@ledgerhq/logs" "^6.12.0" + "@ledgerhq/types-live" "^6.43.0" + axios "^1.3.4" + eip55 "^2.1.1" + react "^18.2.0" + react-dom "^18.2.0" + "@ledgerhq/errors@^5.26.0", "@ledgerhq/errors@^5.50.0": version "5.50.0" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.50.0.tgz#e3a6834cb8c19346efca214c1af84ed28e69dad9" integrity sha512-gu6aJ/BHuRlpU7kgVpy2vcYk6atjB4iauP2ymF7Gk0ez0Y/6VSMVSJvubeEQN+IV60+OBK0JgeIZG7OiHaw8ow== +"@ledgerhq/errors@^6.16.0": + version "6.16.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.16.0.tgz#0aaf16bbf649a3b43867746781b2e3adebf7fe3a" + integrity sha512-vnew6lf4jN6E+WI0DFhD4WY0uM8LYL8HCumtUr86hNwvmEfebi7LxxpJGmYfVQD5TgEC7NibYnQ+2q9XWAc02A== + +"@ledgerhq/evm-tools@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@ledgerhq/evm-tools/-/evm-tools-1.0.11.tgz#d1352dcd3a8e971c5808f6a7e65439277bd1ee66" + integrity sha512-XfOQvEAzT3iD0hd7zNg8kioRXHnWdeLgs2/bwHeI9/pttzE+kTCjLhvIipYAeYVHg0gKaqecoygKdsuh6kS1fw== + dependencies: + "@ledgerhq/cryptoassets" "^11.2.0" + "@ledgerhq/live-env" "^0.7.0" + "@ledgerhq/live-network" "^1.1.9" + crypto-js "4.2.0" + ethers "5.7.2" + "@ledgerhq/hw-app-eth@5.27.2": version "5.27.2" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-5.27.2.tgz#65a2ed613a69340e0cd69c942147455ec513d006" @@ -746,6 +794,33 @@ bignumber.js "^9.0.1" rlp "^2.2.6" +"@ledgerhq/hw-app-eth@^6.35.0": + version "6.35.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-6.35.0.tgz#30486e0e9221de92653985af08a6208fde770a9c" + integrity sha512-BJ39+biwuTXmiKuO2c5PbjJBdGMOSl7nHncuLFCwBXi0hYlHiELHQgEOjjPon418ltuCQyuDBiNMyIFOLikIRQ== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ledgerhq/cryptoassets" "^11.2.0" + "@ledgerhq/domain-service" "^1.1.15" + "@ledgerhq/errors" "^6.16.0" + "@ledgerhq/evm-tools" "^1.0.11" + "@ledgerhq/hw-transport" "^6.30.0" + "@ledgerhq/hw-transport-mocker" "^6.28.0" + "@ledgerhq/logs" "^6.12.0" + "@ledgerhq/types-live" "^6.43.0" + axios "^1.3.4" + bignumber.js "^9.1.2" + +"@ledgerhq/hw-transport-mocker@^6.28.0": + version "6.28.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-mocker/-/hw-transport-mocker-6.28.0.tgz#a664220338b56d62c8aca0a10c98e28484e4a889" + integrity sha512-svUgIRdoc69b49MHncKikoRgWIqn7ZR3IHP+nq4TCTYn2nm5LILJYyf8osnCg8brsXdEY68z++fr++GyF9vUIw== + dependencies: + "@ledgerhq/hw-transport" "^6.30.0" + "@ledgerhq/logs" "^6.12.0" + rxjs "^7.8.1" + "@ledgerhq/hw-transport-node-hid-noevents@^5.26.0": version "5.51.1" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-5.51.1.tgz#71f37f812e448178ad0bcc2258982150d211c1ab" @@ -757,6 +832,17 @@ "@ledgerhq/logs" "^5.50.0" node-hid "2.1.1" +"@ledgerhq/hw-transport-node-hid-noevents@^6.29.0": + version "6.29.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-6.29.0.tgz#10cbb260d9af3e961675ca88695d521dbc8c5964" + integrity sha512-JJM0NGOmFxCJ0IvbGlCo3KHYhkckn7QPNgBlGTrV/UDoMZdtDfp3R971jGUVInUmqYmHRDCGXRpjwgZRI7MJhg== + dependencies: + "@ledgerhq/devices" "^8.1.0" + "@ledgerhq/errors" "^6.16.0" + "@ledgerhq/hw-transport" "^6.30.0" + "@ledgerhq/logs" "^6.12.0" + node-hid "^2.1.2" + "@ledgerhq/hw-transport-node-hid@5.26.0": version "5.26.0" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-5.26.0.tgz#69bc4f8067cdd9c09ef4aed0e0b3c58328936e4b" @@ -771,6 +857,20 @@ node-hid "1.3.0" usb "^1.6.3" +"@ledgerhq/hw-transport-node-hid@^6.28.0": + version "6.28.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-6.28.0.tgz#d8e67b1b8acd1110bcecff11326eb37a7f1f2475" + integrity sha512-kRGsT9YkudP8TbiaBWOtpgMje3gp7CbNHgAA4gdGM5Xri5Li0foEoIFqYZfWCS44NrPbDrsalWqj03HmQ2LDpg== + dependencies: + "@ledgerhq/devices" "^8.1.0" + "@ledgerhq/errors" "^6.16.0" + "@ledgerhq/hw-transport" "^6.30.0" + "@ledgerhq/hw-transport-node-hid-noevents" "^6.29.0" + "@ledgerhq/logs" "^6.12.0" + lodash "^4.17.21" + node-hid "^2.1.2" + usb "2.9.0" + "@ledgerhq/hw-transport-u2f@5.26.0": version "5.26.0" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-5.26.0.tgz#b7d9d13193eb82b051fd7a838cd652372f907ec5" @@ -799,11 +899,62 @@ "@ledgerhq/errors" "^5.50.0" events "^3.3.0" +"@ledgerhq/hw-transport@^6.30.0": + version "6.30.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.30.0.tgz#9c8a8f2c8281fbc4a3db1d1f3ac44a456b38281a" + integrity sha512-wrAwn/wCAaGP2Yuy78cLyqmQNzbuDvUv4gJYF/UO4djvUz0jjvD2w5kxRWxF/W93vyKT+/RplRtFk3CJzD3e3A== + dependencies: + "@ledgerhq/devices" "^8.1.0" + "@ledgerhq/errors" "^6.16.0" + "@ledgerhq/logs" "^6.12.0" + events "^3.3.0" + +"@ledgerhq/live-env@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-env/-/live-env-0.7.0.tgz#b1fd3922111cb9c9410ffbed2010e506adacd093" + integrity sha512-Q77gmJLafjKmc23CbRgBD1Bm1MVatISo0JEWDX/nWZnWUK3IVwp8VxxJDHW4P7TlpsuCKCgCtd0C1gxZDWI/RA== + dependencies: + rxjs "^7.8.1" + utility-types "^3.10.0" + +"@ledgerhq/live-network@^1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-network/-/live-network-1.1.9.tgz#ecf6b14cb382384665b29ffb6d8541044515b8e2" + integrity sha512-uwtVSzL88VtClmfkUTW5plEgdBqXnmT1vhTC7k/bCOf3CPpvkPQ2NLuutT1GHPkHUu+BjAweM6uUKl9JDwGs1g== + dependencies: + "@ledgerhq/errors" "^6.16.0" + "@ledgerhq/live-env" "^0.7.0" + "@ledgerhq/live-promise" "^0.0.3" + "@ledgerhq/logs" "^6.12.0" + axios "0.26.1" + invariant "^2.2.2" + lru-cache "^7.14.1" + +"@ledgerhq/live-promise@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-promise/-/live-promise-0.0.3.tgz#432693468ddd48f94a24437c01791d59d393adbc" + integrity sha512-/49dRz5XoxUw4TFq0kytU2Vz9w+FoGgG28U8RH9nuUWVPjVhAPvhY/QXUQA+7qqaorEIAYPHF0Rappalawhr+g== + dependencies: + "@ledgerhq/logs" "^6.12.0" + "@ledgerhq/logs@^5.26.0", "@ledgerhq/logs@^5.50.0": version "5.50.0" resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.50.0.tgz#29c6419e8379d496ab6d0426eadf3c4d100cd186" integrity sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA== +"@ledgerhq/logs@^6.12.0": + version "6.12.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.12.0.tgz#ad903528bf3687a44da435d7b2479d724d374f5d" + integrity sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA== + +"@ledgerhq/types-live@^6.43.0": + version "6.43.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/types-live/-/types-live-6.43.0.tgz#48491fb6f1a24b012e0b78188de7ad1cd1814bab" + integrity sha512-NvSWPefZ54BLTTMdljO2eS3j1Jbj4O+j/2OWZfyt6T1qMrU1OwORkIn7weuyqR0Y01mTos0sjST7r10MqtauJg== + dependencies: + bignumber.js "^9.1.2" + rxjs "^7.8.1" + "@metamask/eth-sig-util@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088" @@ -1489,7 +1640,7 @@ "@types/node" "*" "@types/responselike" "^1.0.0" -"@types/chai-as-promised@^7.1.3": +"@types/chai-as-promised@^7.1.3", "@types/chai-as-promised@^7.1.8": version "7.1.8" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9" integrity sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw== @@ -1626,6 +1777,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/w3c-web-usb@^1.0.6": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz#cf89cccd2d93b6245e784c19afe0a9f5038d4528" + integrity sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1968,6 +2124,13 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +axios@0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + axios@0.27.2, axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" @@ -1983,7 +2146,7 @@ axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -axios@^1.5.1: +axios@^1.3.4, axios@^1.5.1, axios@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== @@ -2034,7 +2197,7 @@ bigint-crypto-utils@^3.0.23: resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz#72ad00ae91062cf07f2b1def9594006c279c1d77" integrity sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg== -bignumber.js@^9.0.1: +bignumber.js@^9.0.1, bignumber.js@^9.1.2: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== @@ -2608,6 +2771,11 @@ cross-fetch@^3.1.5: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +crypto-js@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + death@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" @@ -2716,6 +2884,11 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== +detect-libc@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + detect-port@^1.3.0: version "1.5.1" resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" @@ -2760,6 +2933,13 @@ dotenv@^16.0.2: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +eip55@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eip55/-/eip55-2.1.1.tgz#28b743c4701ac3c811b1e9fe67e39cf1d0781b96" + integrity sha512-WcagVAmNu2Ww2cDUfzuWVntYwFxbvZ5MvIyLZpMjTTkjD6sCvkGOiS86jTppzu9/gWsc8isLHAeMBWK02OnZmA== + dependencies: + keccak "^3.0.3" + elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -3312,7 +3492,7 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: +follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.0: version "1.15.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== @@ -3984,7 +4164,7 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -invariant@2: +invariant@2, invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -4303,7 +4483,7 @@ keccak@3.0.2: node-gyp-build "^4.2.0" readable-stream "^3.6.0" -keccak@^3.0.0, keccak@^3.0.2: +keccak@^3.0.0, keccak@^3.0.2, keccak@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== @@ -4459,7 +4639,7 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -4492,6 +4672,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.14.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + lru_map@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" @@ -4802,6 +4987,13 @@ node-abi@^2.18.0, node-abi@^2.21.0, node-abi@^2.7.0: dependencies: semver "^5.4.1" +node-abi@^3.3.0: + version "3.51.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.51.0.tgz#970bf595ef5a26a271307f8a4befa02823d4e87d" + integrity sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA== + dependencies: + semver "^7.3.5" + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -4817,6 +5009,11 @@ node-addon-api@^4.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== + node-emoji@^1.10.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -4841,6 +5038,11 @@ node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.7.0.tgz#749f0033590b2a89ac8edb5e0775f95f5ae86d15" integrity sha512-PbZERfeFdrHQOOXiAKOY0VPbykZy90ndPKk0d+CFDegTKmWp1VgOTz2xACVbr1BjCWxrQp68CXtvNsveFhqDJg== +node-gyp-build@^4.5.0: + version "4.7.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.7.1.tgz#cd7d2eb48e594874053150a9418ac85af83ca8f7" + integrity sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg== + node-hid@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-1.3.0.tgz#346a468505cee13d69ccd760052cbaf749f66a41" @@ -4860,6 +5062,15 @@ node-hid@2.1.1: node-addon-api "^3.0.2" prebuild-install "^6.0.0" +node-hid@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-2.2.0.tgz#33e039e7530a7bfe2b7a25f0a2f9496af8b02236" + integrity sha512-vj48zh9j555DZzUhMc8tk/qw6xPFrDyPBH1ST1Z/hWaA/juBJw7IuSxPeOgpzNFNU36mGYj+THioRMt1xOdm/g== + dependencies: + bindings "^1.5.0" + node-addon-api "^3.0.2" + prebuild-install "^7.1.1" + node-port-check@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/node-port-check/-/node-port-check-2.0.1.tgz#72cae367d3ca906b0903b261ce818c297aade11f" @@ -5172,6 +5383,24 @@ prebuild-install@^6.0.0: tar-fs "^2.0.0" tunnel-agent "^0.6.0" +prebuild-install@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -5299,6 +5528,21 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + readable-stream@^2.0.6, readable-stream@^2.2.2: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" @@ -5493,6 +5737,13 @@ rxjs@6: dependencies: tslib "^1.9.0" +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" @@ -5547,6 +5798,13 @@ sc-istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + scrypt-js@3.0.1, scrypt-js@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" @@ -5684,6 +5942,15 @@ simple-get@^3.0.3: once "^1.3.1" simple-concat "^1.0.0" +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -6174,6 +6441,11 @@ tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsort@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" @@ -6362,6 +6634,15 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +usb@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/usb/-/usb-2.9.0.tgz#8ae3b175f93bee559400bff33491eee63406b6a2" + integrity sha512-G0I/fPgfHUzWH8xo2KkDxTTFruUWfppgSFJ+bQxz/kVY2x15EQ/XDB7dqD1G432G4gBG4jYQuF3U7j/orSs5nw== + dependencies: + "@types/w3c-web-usb" "^1.0.6" + node-addon-api "^6.0.0" + node-gyp-build "^4.5.0" + usb@^1.6.3: version "1.9.2" resolved "https://registry.yarnpkg.com/usb/-/usb-1.9.2.tgz#fb6b36f744ecc707a196c45a6ec72442cb6f2b73" @@ -6394,6 +6675,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"