From 8592143c48e776bba98f87cda34ec8541cbe7374 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Fri, 14 Jun 2024 12:17:33 +0300 Subject: [PATCH] Added bridge upgrade migration. Updated README --- README.md | 83 +++++++++++++++++++++++++-- deploy/10_bridge.v2.migration.ts | 33 +---------- deploy/11_bridge.upgrade.migration.ts | 58 +++++++++++++++++++ deploy/1_bridge.migration.ts | 23 +++++--- 4 files changed, 151 insertions(+), 46 deletions(-) create mode 100644 deploy/11_bridge.upgrade.migration.ts diff --git a/README.md b/README.md index 560e60e..781a955 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,18 @@ The first part of Option 3.3, `Circle and the third-party team will jointly coor During the upgrade, the `upgradeToWithSigAndCall` function MUST be used to prevent any security risks during the upgrade process. -### Commands to deploy the BridgeV2 contract +## Commands to upgrade the Bridge contract to BridgeV2 + +Below, you will find two different ways to upgrade the Bridge contract to BridgeV2: + +- [Process of manually upgrading the Bridge contract to BridgeV2](#process-of-manually-upgrading-the-bridge-contract-to-bridgev2) +- [Process of automatically upgrading the Bridge contract to BridgeV2](#process-of-automatic-upgrade-of-the-bridge-contract-to-bridgev2) + +### Process of manually upgrading the Bridge contract to BridgeV2 + +The first step is to deploy the BridgeV2 contract using the process described below. + +#### Commands to deploy the BridgeV2 contract To deploy the BridgeV2 contract on the Ethereum Sepolia, you can run the following command: @@ -106,7 +117,7 @@ npx hardhat migrate --network sepolia --only 10 --verify A list of all available networks can be found in the [hardhat.config.js](https://github.com/qtumproject/bridge-evm-contracts/blob/3c50da4b2a753659de158fb8a1fb975ff3f97bdb/hardhat.config.ts) file. -### Commands to upgrade the Bridge contract to BridgeV2 +#### Upgrade the Bridge contract to BridgeV2 via Etherscan or Gnosis Safe To correctly upgrade the implementation of the Bridge contract to BridgeV2, you first must be the owner of the Bridge contract. @@ -133,7 +144,7 @@ Data to be passed to upgradeToWithSigAndCall as data parameter: 0x7778cd2900000 Again, ensure that you replace `0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF` and `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` with the actual addresses! -### Actual Bridge contract upgrade +#### Actual Bridge contract upgrade via Etherscan After you have the `data` parameter, you can call the `upgradeToWithSigAndCall` function of the Bridge contract on Etherscan. @@ -148,15 +159,75 @@ At this point, you have all the details and can successfully upgrade the Bridge > Ensure that you are the Bridge Owner or have a consensus among validators to perform this action. +#### Bridge contracts upgrade via Gnosis Safe + +The process the same as with Etherscan, but instead of sending the transaction directly, you need to use the [Gnosis Safe Wallet](https://app.safe.global/welcome) + +All you need to do, is to create a transaction to interact with the Bridge contract (should be a Bridge Proxy Address) on the Ethereum Network. + +Bridge ABI can be found [here](https://github.com/qtumproject/bridge-evm-contracts/blob/db8288800fab385135af837cd757456b520bb414/abi/Bridge.json) + +After that, you can pass there an ABI, select `upgradeToWithSigAndCall` function, and fill the parameters with the data you calculated before. + +There, you will need to provide three parameters: +- `newImplementation_`: The address of the new implementation contract. In this case, it is the newly deployed `BridgeV2` contract address. +- `signatures_`: As soon as a Gnosis Safe account is employed, the signatures are not needed, so you need to pass an empty array. +- `data_`: The initialization calldata that will be used to perform a call to immediately initialize the proxy contract. + +After, you could create a transaction and sign it with the Gnosis Safe account owners, reach a threshold and eventually execute it. + +### Process of Automatic Upgrade of the Bridge Contract to BridgeV2 + +For this method to work, you MUST meet one of the following criteria: +- Be the owner of the Bridge contract and have access to the private key of the owner account. +- Gather a consensus among validators to upgrade the Bridge contract and receive the required number of signatures. + +#### Commands to Upgrade the Bridge Contract to BridgeV2 + +To understand why `USDC_TOKEN_ADDRESS` and `CIRCLE_TRUSTED_ACCOUNT` are needed, refer to the [Ability to burn locked USDC](https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#2-ability-to-burn-locked-usdc) section of the [Bridged USDC Standard](https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#bridged-usdc-standard) document. + +In the `.env` file, you need to add the following parameters: +- `PRIVATE_KEY` - The private key of the owner account that has enough balance to deploy the BridgeV2 contract and call the `upgradeToWithSigAndCall` function. + - In case you are not the owner but have gathered enough signatures, you only need enough balance to cover the transaction fees. +- `USDC_TOKEN_ADDRESS` - The address of the USDC token contract. +- `CIRCLE_TRUSTED_ACCOUNT` - The address of the Circle Trusted Account. +- `BRIDGE_ADDRESS` - The address of the Bridge contract that you want to upgrade to BridgeV2. +- `SIGNATURES` - The number of signatures required to upgrade the Bridge contract to BridgeV2. + - This option is needed only if the `isSignersMode` flag is set to `true` in the Bridge contract. + +After that, you can run the following command to upgrade the Bridge contract to BridgeV2 on the Ethereum Sepolia network: + +```bash +npx hardhat migrate --network sepolia --from 10 +``` + +If you made a mistake in the variables configuration and migration 11 failed, you can fix those and then only run migration 11. + +But, before that, you need to set the `BRIDGE_V2_ADDRESS` variable in the `.env` file to the address of the newly deployed BridgeV2 contract. + +After that, you can run the following command to upgrade the Bridge contract to BridgeV2 on the Ethereum Sepolia network: + +```bash +npx hardhat migrate --network sepolia --only 11 +``` + --- After the steps above (pausing and implementation upgrade), the Circle team can proceed with their part of burning the locked USDC tokens. This step concludes the USDC Hand Over Procedure. -## Bridge Management Methods +## Afterward BridgeV2 Contract Upgrade + +After the Bridge is upgraded to V2, the Circle team can proceed with their part of burning the locked USDC tokens. + +Make sure to upgrade Validator Nodes to stop supporting the USDC token. + +After the Validators are upgraded, the Bridge can be unpaused on both sides and continue working with other supported tokens, if any. + +# Bridge Management Methods -### Usage +## Usage All the functions below should be called directly on the Bridge contract. @@ -166,7 +237,7 @@ All the functions below should be called directly on the Bridge contract. Bridge ABI can be found [here](https://github.com/qtumproject/bridge-evm-contracts/blob/db8288800fab385135af837cd757456b520bb414/abi/Bridge.json) -### Methods +## Methods To check the current owner of the Bridge contract, you can call the `owner` method. To verify if the `signersMode` is enabled, use the `isSignersMode` method. diff --git a/deploy/10_bridge.v2.migration.ts b/deploy/10_bridge.v2.migration.ts index 401d634..3a6143f 100644 --- a/deploy/10_bridge.v2.migration.ts +++ b/deploy/10_bridge.v2.migration.ts @@ -2,37 +2,8 @@ import { Deployer, Reporter } from "@solarity/hardhat-migrate"; import { BridgeV2__factory } from "@ethers-v6"; -import { deployQTumContract } from "@/scripts/qtumDeploy"; - -const networkMap: Record = { - "81": qtumDeployment, - "8889": qtumDeployment, - "1": ethereumDeployment, - "11155111": ethereumDeployment, -}; - export = async (deployer: Deployer) => { - const chainId = await deployer.getChainId(); - - let bridgeImplementationAddress: string; - - if (!networkMap[chainId.toString()]) { - throw new Error(`ChainId ${chainId} not supported`); - } - - [bridgeImplementationAddress] = await networkMap[chainId.toString()](deployer); - - Reporter.reportContracts(["Bridge V2 Implementation", bridgeImplementationAddress]); -}; - -async function qtumDeployment(_deployer: Deployer): Promise<[string]> { - const bridgeImplementation = await deployQTumContract(BridgeV2__factory, "Bridge V2 Implementation"); - - return [bridgeImplementation.address as string]; -} - -async function ethereumDeployment(deployer: Deployer): Promise<[string]> { const bridgeImplementation = await deployer.deploy(BridgeV2__factory); - return [await bridgeImplementation.getAddress()]; -} + Reporter.reportContracts(["Bridge V2 Implementation", await bridgeImplementation.getAddress()]); +}; diff --git a/deploy/11_bridge.upgrade.migration.ts b/deploy/11_bridge.upgrade.migration.ts new file mode 100644 index 0000000..670f9e1 --- /dev/null +++ b/deploy/11_bridge.upgrade.migration.ts @@ -0,0 +1,58 @@ +import { Deployer } from "@solarity/hardhat-migrate"; + +import { Bridge, Bridge__factory, BridgeV2, BridgeV2__factory } from "@ethers-v6"; + +/** + * This part of the migration script can be run independently of the previous migrations, as well as immediately after the migration 10. + * + * If you intend to run the migration after the migration 10, you MUST set the BRIDGE_ADDRESS environment variable to the address of the Bridge contract (its Proxy). + * + * If you want to run the migration exclusively, you MUST set both the BRIDGE_ADDRESS and BRIDGE_V2_ADDRESS environment variables to the addresses of the Bridge contract (its Proxy) and the BridgeV2 contract, respectively. + */ +export = async (deployer: Deployer) => { + const bridge: Bridge = await deployer.deployed(Bridge__factory, process.env.BRIDGE_ADDRESS || "Bridge Proxy"); + const bridgeImplementation: BridgeV2 = await deployer.deployed(BridgeV2__factory, process.env.BRIDGE_V2_ADDRESS); + + await checkIfUpgradeIsPossible(deployer, bridge); + + const initCallData = BridgeV2__factory.createInterface().encodeFunctionData("__USDCManager_init(address,address)", [ + process.env.USDC_TOKEN_ADDRESS!, + process.env.CIRCLE_TRUSTED_ACCOUNT!, + ]); + + const isSignerModeEnabled = await bridge.isSignersMode(); + + if (isSignerModeEnabled) { + const signatures = process.env.SIGNATURES!.split(","); + + await bridge.upgradeToWithSigAndCall(await bridgeImplementation.getAddress(), signatures, initCallData); + } else { + await bridge.upgradeToWithSigAndCall(await bridgeImplementation.getAddress(), [], initCallData); + } +}; + +async function checkIfUpgradeIsPossible(deployer: Deployer, bridge: Bridge) { + const signer = await deployer.getSigner(); + const currentBridgeOwner = await bridge.owner(); + const isSignerModeEnabled = await bridge.isSignersMode(); + + if (isSignerModeEnabled && process.env.SIGNATURES === undefined) { + throw new Error( + "The Bridge contract is in Signers mode and SIGNATURES environment variable is not set. Upgrade is not possible by the EOA.", + ); + } + + if (currentBridgeOwner !== (await signer.getAddress()) && !isSignerModeEnabled) { + throw new Error( + `The Bridge contract is not owned by the deployer: ${await signer.getAddress()}. Current owner: ${currentBridgeOwner}`, + ); + } + + if (process.env.USDC_TOKEN_ADDRESS === undefined) { + throw new Error("USDC_TOKEN_ADDRESS environment variable is not set"); + } + + if (process.env.CIRCLE_TRUSTED_ACCOUNT === undefined) { + throw new Error("CIRCLE_TRUSTED_ACCOUNT environment variable is not set"); + } +} diff --git a/deploy/1_bridge.migration.ts b/deploy/1_bridge.migration.ts index 5ece4ad..67b5eb5 100644 --- a/deploy/1_bridge.migration.ts +++ b/deploy/1_bridge.migration.ts @@ -14,6 +14,7 @@ const networkMap: Record = { "81": qtumDeployment, "8889": qtumDeployment, "1": ethereumDeployment, + "31337": ethereumDeployment, "11155111": ethereumDeployment, }; @@ -54,15 +55,19 @@ async function qtumDeployment(_deployer: Deployer): Promise<[string, string]> { async function ethereumDeployment(deployer: Deployer): Promise<[string, string]> { const bridgeImplementation = await deployer.deploy(Bridge__factory); - const proxy = await deployer.deploy(ERC1967Proxy__factory, [ - await bridgeImplementation.getAddress(), - bridgeImplementation.interface.encodeFunctionData("__Bridge_init", [ - validators, - ethers.ZeroAddress, - threshold, - false, - ]), - ]); + const proxy = await deployer.deploy( + ERC1967Proxy__factory, + [ + await bridgeImplementation.getAddress(), + bridgeImplementation.interface.encodeFunctionData("__Bridge_init", [ + validators, + ethers.ZeroAddress, + threshold, + false, + ]), + ], + { name: "Bridge Proxy" }, + ); const bridge = await deployer.deployed(Bridge__factory, await proxy.getAddress());