Skip to content

Commit

Permalink
Added bridge upgrade migration. Updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
KyrylR committed Jun 14, 2024
1 parent c60641c commit 8592143
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 46 deletions.
83 changes: 77 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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.

Expand All @@ -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.

Expand All @@ -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.

Expand All @@ -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.
Expand Down
33 changes: 2 additions & 31 deletions deploy/10_bridge.v2.migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any> = {
"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()]);
};
58 changes: 58 additions & 0 deletions deploy/11_bridge.upgrade.migration.ts
Original file line number Diff line number Diff line change
@@ -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");
}
}
23 changes: 14 additions & 9 deletions deploy/1_bridge.migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const networkMap: Record<string, any> = {
"81": qtumDeployment,
"8889": qtumDeployment,
"1": ethereumDeployment,
"31337": ethereumDeployment,
"11155111": ethereumDeployment,
};

Expand Down Expand Up @@ -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());

Expand Down

0 comments on commit 8592143

Please sign in to comment.