From cc7970e886af848c7915d8a8fd812d45e2277364 Mon Sep 17 00:00:00 2001 From: todd <81545601+todd-woko@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:43:55 +0800 Subject: [PATCH] feat: bridge contract migrate (#238) --- .../contracts/bridge/FxBridgeMigrateLogic.sol | 41 +++++ .../bridge/FxBridgeMigrateLogicETH.sol | 41 +++++ solidity/hardhat.config.ts | 36 ++++- solidity/tasks/contract_task.ts | 145 +++++++++++++++++- solidity/tasks/subtasks.ts | 23 +++ 5 files changed, 277 insertions(+), 9 deletions(-) create mode 100644 solidity/contracts/bridge/FxBridgeMigrateLogic.sol create mode 100644 solidity/contracts/bridge/FxBridgeMigrateLogicETH.sol diff --git a/solidity/contracts/bridge/FxBridgeMigrateLogic.sol b/solidity/contracts/bridge/FxBridgeMigrateLogic.sol new file mode 100644 index 000000000..2be382639 --- /dev/null +++ b/solidity/contracts/bridge/FxBridgeMigrateLogic.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +pragma experimental ABIEncoderV2; +pragma solidity ^0.8.0; + +import {FxBridgeLogic} from "./FxBridgeLogic.sol"; + +/* solhint-disable custom-errors */ +contract FxBridgeMigrateLogic is FxBridgeLogic { + /* =============== MIGRATE =============== */ + function migrateInit( + bytes32 _fxBridgeId, + uint256 _powerThreshold, + uint256 _lastEventNonce, + bytes32 _lastOracleSetCheckpoint, + uint256 _lastOracleSetNonce, + address[] memory _bridgeTokens, + uint256[] memory _lastBatchNonces, + TokenStatus[] memory _tokenStatuses + ) public initializer { + __Pausable_init(); + __Ownable_init(); + __ReentrancyGuard_init(); + require( + _lastBatchNonces.length == _bridgeTokens.length && + _bridgeTokens.length == _tokenStatuses.length, + "Malformed last batch token information." + ); + state_fxBridgeId = _fxBridgeId; + state_powerThreshold = _powerThreshold; + state_lastEventNonce = _lastEventNonce; + state_lastOracleSetCheckpoint = _lastOracleSetCheckpoint; + state_lastOracleSetNonce = _lastOracleSetNonce; + for (uint256 i = 0; i < _bridgeTokens.length; i++) { + bridgeTokens.push(_bridgeTokens[i]); + state_lastBatchNonces[_bridgeTokens[i]] = _lastBatchNonces[i]; + tokenStatus[_bridgeTokens[i]] = _tokenStatuses[i]; + } + version = "1.0.0"; + } +} diff --git a/solidity/contracts/bridge/FxBridgeMigrateLogicETH.sol b/solidity/contracts/bridge/FxBridgeMigrateLogicETH.sol new file mode 100644 index 000000000..daa08b162 --- /dev/null +++ b/solidity/contracts/bridge/FxBridgeMigrateLogicETH.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma experimental ABIEncoderV2; +pragma solidity ^0.8.0; + +import {FxBridgeLogicETH} from "./FxBridgeLogicETH.sol"; + +/* solhint-disable custom-errors */ +contract FxBridgeMigrateLogicETH is FxBridgeLogicETH { + /* =============== MIGRATE =============== */ + function migrateInit( + bytes32 _fxBridgeId, + uint256 _powerThreshold, + uint256 _lastEventNonce, + bytes32 _lastOracleSetCheckpoint, + uint256 _lastOracleSetNonce, + address[] memory _bridgeTokens, + uint256[] memory _lastBatchNonces, + TokenStatus[] memory _tokenStatuses + ) public initializer { + __Pausable_init(); + __Ownable_init(); + __ReentrancyGuard_init(); + require( + _lastBatchNonces.length == _bridgeTokens.length && + _bridgeTokens.length == _tokenStatuses.length, + "Malformed last batch token information." + ); + state_fxBridgeId = _fxBridgeId; + state_powerThreshold = _powerThreshold; + state_lastEventNonce = _lastEventNonce; + state_lastOracleSetCheckpoint = _lastOracleSetCheckpoint; + state_lastOracleSetNonce = _lastOracleSetNonce; + for (uint256 i = 0; i < _bridgeTokens.length; i++) { + bridgeTokens.push(_bridgeTokens[i]); + state_lastBatchNonces[_bridgeTokens[i]] = _lastBatchNonces[i]; + tokenStatus[_bridgeTokens[i]] = _tokenStatuses[i]; + } + version = "1.0.0"; + } +} diff --git a/solidity/hardhat.config.ts b/solidity/hardhat.config.ts index 197f6a725..a88082c52 100644 --- a/solidity/hardhat.config.ts +++ b/solidity/hardhat.config.ts @@ -12,17 +12,25 @@ const config: HardhatUserConfig = { networks: { hardhat: { // forking: { - // url: `${process.env.MAINNET_URL || "https://mainnet.infura.io/v3/infura-key"}`, + // url: `${process.env.MAINNET_URL || "https://mainnet.infura.io/v3/"+process.env.INFURA_KEY}`, // } chainId: 1337, }, mainnet: { - url: `${process.env.MAINNET_URL || "https://mainnet.infura.io/v3/infura-key"}`, + url: `${process.env.MAINNET_URL || "https://mainnet.infura.io/v3/"+process.env.INFURA_KEY}`, chainId: 1, }, - goerli: { - url: `${process.env.GOERLI_URL || "https://goerli.infura.io/v3/infura-key"}`, - chainId: 5, + sepolia: { + url: `${process.env.SEPOLIA_URL || "https://sepolia.infura.io/v3/"+process.env.INFURA_KEY}`, + chainId: 11155111, + }, + arbitrumSepolia: { + url: `${process.env.ARBITRUM_URL || "https://sepolia-rollup.arbitrum.io/rpc"}`, + chainId: 421614, + }, + optimisticSepolia: { + url: `${process.env.OPTIMISTIC_URL || "https://sepolia.optimism.io"}`, + chainId: 11155420, }, localhost: { url: `${process.env.LOCAL_URL || "http://127.0.0.1:8545"}`, @@ -61,9 +69,21 @@ const config: HardhatUserConfig = { }, etherscan: { apiKey: { - mainnet: `${process.env.ETHERSCAN_API_KEY || "scan-key"}`, - goerli: `${process.env.ETHERSCAN_API_KEY || "scan-key"}`, - } + mainnet: `${process.env.ETHERSCAN_API_KEY}`, + sepolia: `${process.env.ETHERSCAN_API_KEY}`, + arbitrumSepolia: `${process.env.ETHERSCAN_API_KEY}`, + optimisticSepolia: `${process.env.ETHERSCAN_API_KEY}`, + }, + customChains: [ + { + network: "optimisticSepolia", + chainId: 11155420, + urls: { + apiURL: "https://api-sepolia-optimistic.etherscan.io/api", + browserURL: "https://sepolia-optimism.etherscan.io/" + } + } + ] }, dependencyCompiler: { paths: [ diff --git a/solidity/tasks/contract_task.ts b/solidity/tasks/contract_task.ts index ca92c46a1..a29dc83b3 100644 --- a/solidity/tasks/contract_task.ts +++ b/solidity/tasks/contract_task.ts @@ -2,12 +2,14 @@ import {task} from "hardhat/config"; import {string} from "hardhat/internal/core/params/argumentTypes"; import { AddTxParam, + BridgeStateInfoToJson, SUB_CHECK_PRIVATE_KEY, SUB_CONFIRM_TRANSACTION, SUB_CREATE_TRANSACTION, SUB_GET_CONTRACT_ADDR, TransactionToJson } from "./subtasks"; +import {FxBridgeLogic, IFxBridgeLogic} from "../typechain-types"; const deploy = task("deploy-contract", "deploy contract") .addParam("contractName", "deploy contract name", undefined, string, false) @@ -47,4 +49,145 @@ const deploy = task("deploy-contract", "deploy contract") } }); -AddTxParam([deploy]) \ No newline at end of file +const migrateBridge = task("migrate-bridge", "migrate bridge") + .addParam("oldBridge", "old bridge address", undefined, string, false) + .addParam("oldRpc", "old rpc url", undefined, string, false) + .addParam("newBridgeProxy", "new bridge proxy address", undefined, string, true) + .addParam("newBridgeLogic", "new bridge logic address", undefined, string, true) + .addParam("admin", "admin address", undefined, string, true) + .setAction(async (taskArgs, hre) => { + let {oldBridge, oldRpc, newBridgeProxy, newBridgeLogic, admin} = taskArgs + const provider = new hre.ethers.JsonRpcProvider(oldRpc); + const oldBridgeContract = await hre.ethers.getContractAt("FxBridgeLogic", oldBridge) as FxBridgeLogic; + const fxBridgeId = await oldBridgeContract.connect(provider).state_fxBridgeId() + const powerThreshold = await oldBridgeContract.connect(provider).state_powerThreshold() + const lastEventNonce = await oldBridgeContract.connect(provider).state_lastEventNonce() + const lastOracleSetNonce = await oldBridgeContract.connect(provider).state_lastOracleSetNonce() + const lastOracleSetCheckpoint = await oldBridgeContract.connect(provider).state_lastOracleSetCheckpoint() + const bridgeTokens = await oldBridgeContract.connect(provider).getBridgeTokenList() + + let bridgeTokenAddress: string[] = []; + let lastBatchNonce: bigint[] = []; + let tokenStatus: IFxBridgeLogic.TokenStatusStruct[] = []; + + for (let i = 0; i < bridgeTokens.length; i++) { + const token = bridgeTokens[i]; + bridgeTokenAddress.push(token.addr); + const batchNonce = await oldBridgeContract.connect(provider).state_lastBatchNonces(token.addr) + lastBatchNonce.push(batchNonce); + const status = await oldBridgeContract.connect(provider).tokenStatus(token.addr) + tokenStatus.push(status); + } + + const bridgeMigrateLogicFactory: any = await hre.ethers.getContractFactory("FxBridgeMigrateLogic") + let proxyFactory: any = await hre.ethers.getContractFactory("TransparentUpgradeableProxy") + const initData = bridgeMigrateLogicFactory.interface.encodeFunctionData('migrateInit', [ + fxBridgeId, + powerThreshold, + lastEventNonce, + lastOracleSetCheckpoint, + lastOracleSetNonce, + bridgeTokenAddress, + lastBatchNonce, + tokenStatus + ]) + let {answer} = await hre.run(SUB_CONFIRM_TRANSACTION, { + message: `\n${BridgeStateInfoToJson( + fxBridgeId, + powerThreshold, + lastEventNonce, + lastOracleSetNonce, + lastOracleSetCheckpoint, + bridgeTokenAddress, + lastBatchNonce, + tokenStatus + )}\n`, + disableConfirm: taskArgs.disableConfirm, + }); + if (!answer) return; + + const {wallet} = await hre.run(SUB_CHECK_PRIVATE_KEY, taskArgs); + if (!newBridgeLogic) { + const paramData = bridgeMigrateLogicFactory.interface.encodeDeploy([]); + const data = bridgeMigrateLogicFactory.bytecode + paramData.slice(2); + const tx = await hre.run(SUB_CREATE_TRANSACTION, { + from: await wallet.getAddress(), data: data, + gasPrice: taskArgs.gasPrice, + maxFeePerGas: taskArgs.maxFeePerGas, + maxPriorityFeePerGas: taskArgs.maxPriorityFeePerGas, + nonce: taskArgs.nonce, + gasLimit: taskArgs.gasLimit, + }); + + answer = await hre.run(SUB_CONFIRM_TRANSACTION, { + message: `\n${TransactionToJson(tx)}\n`, + disableConfirm: taskArgs.disableConfirm, + }); + if (!answer) return; + + try { + const deployTx = await wallet.sendTransaction(tx); + const receipt = await deployTx.wait(); + newBridgeLogic = receipt.contractAddress; + console.log(`deploy bridge logic, ${newBridgeLogic}`) + } catch (e) { + console.log(`Deploy failed, ${e}`) + return; + } + } + admin = admin || await wallet.getAddress() + if (!newBridgeProxy) { + const paramData = proxyFactory.interface.encodeDeploy([newBridgeLogic, admin, initData]); + const data = proxyFactory.bytecode + paramData.slice(2); + const tx = await hre.run(SUB_CREATE_TRANSACTION, { + from: await wallet.getAddress(), data: data, + gasPrice: taskArgs.gasPrice, + maxFeePerGas: taskArgs.maxFeePerGas, + maxPriorityFeePerGas: taskArgs.maxPriorityFeePerGas, + nonce: taskArgs.nonce, + gasLimit: taskArgs.gasLimit, + }); + + answer = await hre.run(SUB_CONFIRM_TRANSACTION, { + message: `\n${TransactionToJson(tx)}\n`, + disableConfirm: taskArgs.disableConfirm, + }); + if (!answer) return; + + try { + const deployTx = await wallet.sendTransaction(tx); + const receipt = await deployTx.wait(); + newBridgeProxy = receipt.contractAddress; + console.log(`deploy proxy, ${newBridgeProxy}`) + } catch (e) { + console.log(`Deploy failed, ${e}`) + return; + } + } else { + proxyFactory = await hre.ethers.getContractAt("ITransparentUpgradeableProxy", newBridgeProxy, wallet) + const data = proxyFactory.interface.encodeFunctionData('upgradeToAndCall', [newBridgeLogic, initData]) + const tx = await hre.run(SUB_CREATE_TRANSACTION, { + from: await wallet.getAddress(), to: newBridgeProxy, data: data, + gasPrice: taskArgs.gasPrice, + maxFeePerGas: taskArgs.maxFeePerGas, + maxPriorityFeePerGas: taskArgs.maxPriorityFeePerGas, + nonce: taskArgs.nonce, + gasLimit: taskArgs.gasLimit, + }); + + answer = await hre.run(SUB_CONFIRM_TRANSACTION, { + message: `\n${TransactionToJson(tx)}\n`, + disableConfirm: taskArgs.disableConfirm, + }); + if (!answer) return; + + try { + const migrateTx = await wallet.sendTransaction(tx); + await migrateTx.wait(); + console.log(`migrate success, ${migrateTx.hash}`) + } catch (e) { + console.log(`migrate failed, ${e}`) + } + } + }) +AddTxParam([deploy, migrateBridge]) \ No newline at end of file diff --git a/solidity/tasks/subtasks.ts b/solidity/tasks/subtasks.ts index 1c52f497e..047383fce 100644 --- a/solidity/tasks/subtasks.ts +++ b/solidity/tasks/subtasks.ts @@ -6,6 +6,7 @@ import {ConfigurableTaskDefinition} from "hardhat/types"; import {boolean, string} from "hardhat/internal/core/params/argumentTypes"; import TransportNodeHid from "@ledgerhq/hw-transport-node-hid"; import inquirer from 'inquirer'; +import {IFxBridgeLogic} from "../typechain-types"; // sub task name export const SUB_CHECK_PRIVATE_KEY: string = "sub:check-private-key"; @@ -239,6 +240,28 @@ export function TransactionToJson(transaction: TransactionLike): string { }, null, 2); } +export function BridgeStateInfoToJson( + fxBridgeId: string, + powerThreshold: bigint, + lastEventNonce: bigint, + lastOracleSetNonce: bigint, + lastOracleSetCheckpoint: string, + bridgeTokenAddress: string[], + lastBatchNonce: bigint[], + tokenStatus: IFxBridgeLogic.TokenStatusStruct[] +): string { + return JSON.stringify({ + fxBridgeId: fxBridgeId, + powerThreshold: powerThreshold.toString(), + lastEventNonce: lastEventNonce.toString(), + lastOracleSetNonce: lastOracleSetNonce.toString(), + lastOracleSetCheckpoint: lastOracleSetCheckpoint, + bridgeTokenAddress: bridgeTokenAddress.toString(), + lastBatchNonce: lastBatchNonce.toString(), + tokenStatus: tokenStatus.toString() + }, null, 2); +} + export const vote_power = 2834678415 type Oracle = {