Skip to content
Jon Roethke edited this page Feb 9, 2022 · 16 revisions

ChainBridge migration guide

This migration guide describes necessary steps that need to be executed to migrate from v1 to v2 of ChainBridge successfully. This guide is only applicable if the bridge is being used for EVM-compatible chains.

Bridge v2 deployment

Before starting migration itself, you first need to deploy v2 of ChainBridge. You can find a detailed guide on deployment here. After successful deployment, you can begin the migration process.

Migration

The steps defined below describe the migration process. To complete the entire migration process, steps 2 and 3 need to be executed for each chain that is bridged by deployed v1 ChainBridge.

1) Stopping bridge

Firstly we need to stop v1 of ChainBridge. This should be communicated to the bridge users in advance, as some downtime is necessary to execute a successful migration (downtime duration mainly depends on the number of assets that are being migrated).

a) Disable new deposits

Creating new deposits should be disabled on the bridge frontend. This step will stop the influx of new deposit requests.

b) Resolve pending requests

Now it is necessary to resolve all pending requests. If done manually, the administrator should monitor all pending proposals until the relayers have voted and executed them (or proposals have been canceled). If there is a problem with some proposal, the administrator should manually resolve this proposal.

Deposit monitoring can be achieved by parsing proposal events (ProposalEvent) and checking for each proposal if it has been executed (meaning proposal status is set to Executed or Cancelled).

The administrator can use a pre-made script to ease up this process. After creating the script configuration, you can run it with make stop-bridge (find more details on how to run the script in the Scripts section).

This script parses ProposalEvent from the bridge contract and checks if there are any pending proposals (meaning the latest proposal status is set to Active or Passed). It can check for pending proposals on multiple chains, depending on the configuration. If there are some pending proposals, their details are displayed. It reruns this check every 60 seconds and refreshes the information on pending proposals. If configuration property autoPauseBridge is set to true, and all proposals have been resolved, the script will also call bridge contract function adminPauseTransfers for each chain (effectively stopping v1 of ChainBridge).

Below you can see an example of executing this script:

make stop-bridge
go run main.go stop-bridge

Starting ChainBridge scripts: stop-bridge
-----------------------------------------------------------
Loading configuration from path: /../../../configuration.json
Successfully loaded configuration!
-----------------------------------------------------------
Loading configuration from path: /../../../repos/v1-chainbridge/config.json
Successfully loaded v1BridgeConfig!
-----------------------------------------------------------
Checking for pending proposals on chain Goerli ...
Querying for proposals from block: 6200000
0 pending deposits:
-----------------------------------------------------------
Checking for pending proposals on chain Rinkeby ...
Querying for proposals from block: 10087009
2 pending deposits:
-----------------------------------------------------------
[0] Status: Active OriginChainID: 0 DepositNonce: 1 ResourceID: 0x000000000000000000000000000000c76ebe4a02bbc34786d860b355f5a5ce00 DataHash: 0x0517878e669360d8df771075b44fe573c86f57c206db2e7619a32a96b9c01225 
    => Event: ProposalEvent BlockNumber: 10087316 TxHash: 0xdca2cafaab3705f917ffbf98abbc314f8f6d7639274afca07ec2625528974ca8
[1] Status: Passed OriginChainID: 0 DepositNonce: 2 ResourceID: 0x000000000000000000000000000000c76ebe4a02bbc34786d860b355f5a5ce00 DataHash: 0x0517878e669360d8df771075b44fe573c86f57c206db2e7619a32a96b9c01225 
    => Event: ProposalEvent BlockNumber: 10087313 TxHash: 0xddb60fd2047289624872cff2e8d320919371c4aa7a8e5856dda96da5c899ffc8
-----------------------------------------------------------
Waiting for 60 seconds....
-----------------------------------------------------------
Checking for pending proposals on chain Goerli ...
Querying for proposals from block: 6200000
0 pending deposits:
-----------------------------------------------------------
Checking for pending proposals on chain Rinkeby ...
Querying for proposals from block: 10087009
1 pending deposits:
-----------------------------------------------------------
[0] Status: Passed OriginChainID: 0 DepositNonce: 1 ResourceID: 0x000000000000000000000000000000c76ebe4a02bbc34786d860b355f5a5ce00 DataHash: 0x0517878e669360d8df771075b44fe573c86f57c206db2e7619a32a96b9c01225 
    => Event: ProposalEvent BlockNumber: 10087316 TxHash: 0xdca2cafaab3705f917ffbf98abbc314f8f6d7639274afca07ec2625528974ca8
-----------------------------------------------------------
Waiting for 60 seconds....
-----------------------------------------------------------
Checking for pending proposals on chain Goerli ...
Querying for proposals from block: 6200000
0 pending deposits:
-----------------------------------------------------------
Checking for pending proposals on chain Rinkeby ...
Querying for proposals from block: 10087009
0 pending deposits:
-----------------------------------------------------------
All proposals have been resolved!
-----------------------------------------------------------
Transaction for pausing bridge contract on chain Goerli submitted with hash 0xddb60fd2047289624872cff2e8d320919371c4aa7a8e5856dda96da5c899ffc8
Transaction for pausing bridge contract on chain Rinkeby submitted with hash 0x2a29a63b37ff7d0baf63a268fde7acd94bd127a8beed8d643f91868aa5c698d8

Note: This script is just monitoring all pending proposals. If some proposal is stuck for some reason, the administrator needs to resolve it manually.

2) Liquidity migration

The steps described below need to be executed for each asset you want to migrate from v1 to v2 ChainBridge. Remember to first deploy v2 ChainBridge before doing these steps!

a) Register resource

Firstly you need to register a new resource on the v2 bridge. You can find instructions for this inside deployment guide here.

b.1) Migrate resource (ERC20)

There are two distinct types of assets that can be bridged.

Mint/Burn tokens

The first type is tokens burned on the source chain when the deposit is executed and then minted on the destination chain. All you need to do for these tokens is add new appropriate handlers as minters/burners on v2 of ChainBridge. You can find instructions for this inside deployment guide here.

Locked tokens

The second type is tokens locked on the source chain when the deposit is executed and then released (sent) on the destination chain. You need to withdraw all liquidity from the v1 handler for these tokens and send it to the v2 handler.

Withdrawing is executed by invoking function adminWithdraw on the v1 bridge contract. Before invoking this function, you should check the current balance of a handler so that you will know the full amount to withdraw everything from the handler.

These funds need to be sent to the new handler on the v2 bridge. You can provide v2 handler address as argument recipient when executing function adminWithdraw and by doing so execute withdrawal and transfer in one transaction.

The administrator can use a pre-made script to facilitate this process of migrating locked tokens. After creating the script configuration (defining details for all tokens being transferred), you can run it with make transfer-tokens (find more details on how to define the configuration and run the script in the Scripts section).

Below is an example of executing this script:

make transfer-tokens
go run main.go transfer-tokens

Starting ChainBridge scripts: transfer-tokens
-----------------------------------------------------------
Loading configuration from path: /../../../chainsafe/chainbridge-migration/configuration.json
Successfully loaded configuration!
-----------------------------------------------------------
Loading configuration from path: /../../../chbridge-migration/chainbridge/config.json
Successfully loaded v1BridgeConfig!
-----------------------------------------------------------
Executing token transfer on the chain Goerli ...
[0] Transfer of ERC20 token 0xaFF4481D10270F50f203E0763e2597776068CBc5
        Amount/TokenID: 100
        To: 0xff9f4a4Fc82A803bD00052Ed5b90366c8cDa622b
        Submitted with hash 0x50513e6581e7e72ca26c6d3a46e94872d2ffaf4b35f67c451690072a441d9f3c on the chain Goerli
-----------------------------------------------------------
Executing token transfer on the chain Rinkeby ...
[0] Transfer of ERC20 token 0x53fb43bae4c13d6afad37fb37c3fc49f3af433f5
        Amount/TokenID: 100
        To: 0xaD639dcdaE2E56eeD45Ab7aEa061aA1274f90fC9
        Submitted with hash 0x49f1a167d0362a0a3f8caa4052697bbd64fd41fbac0e39c4a4044db719b7c26c on the chain Rinkeby
-----------------------------------------------------------

b.2) Migrate resource (ERC721)

Same as with the ERC20 tokens, there are two distinct types of assets that can be bridged.

Mint/Burn tokens

Like ERC20 tokens, ERC721 tokens are also burned on the source chain when the deposit is being executed and then minted on the destination chain. All you need to do for these tokens is add new appropriate handlers as minters/burners on v2 of ChainBridge. You can find instructions for this inside deployment guide here.

Locked tokens

The second type is tokens locked on the source chain when the deposit is being executed and then released (sent) on the destination chain. You need to withdraw every token from v1 handler and send it to the v2 handler.

Withdrawing is executed by invoking function adminWithdraw on the v1 bridge contract. Just as is done with ERC20 tokens, the same is also done with ERC721s whereby you can provide the v2 handler address as argument recipient when executing function adminWithdraw and execute withdrawal and transfer in one transaction.

The difference with bridging ERC721 tokens versus ERC20 tokens is that it is impossible to withdraw all ERC721 tokens in one transaction, as we need to invoke the adminWithdraw function for each token locked by the v1 handler.

The administrator can use a pre-made script to facilitate this process of migrating locked tokens. After creating the script configuration (defining details for all tokens being transferred), you can run it with make transfer-tokens (find more details on how to define the configuration and run the script in Scripts section).

Below you can see an example of executing this script:

make transfer-tokens
go run main.go transfer-tokens

Starting ChainBridge scripts: transfer-tokens
-----------------------------------------------------------
Loading configuration from path: /../../../chainsafe/chainbridge-migration/configuration.json
Successfully loaded configuration!
-----------------------------------------------------------
Loading configuration from path: /../../../chbridge-migration/chainbridge/config.json
Successfully loaded v1BridgeConfig!
-----------------------------------------------------------
Executing token transfer on the chain Goerli ...
[0] Transfer of ERC721 token 0xaFF4481D10270F50f203E0763e2597776068CBc5
        Amount/TokenID: 2
        To: 0xff9f4a4Fc82A803bD00052Ed5b90366c8cDa622b
        Submitted with hash 0x50513e6581e7e72ca26c6d3a46e94872d2ffaf4b35f67c451690072a441d9f3c on the chain Goerli
[1] Transfer of ERC721 token 0xaFF4481D10270F50f203E0763e2597776068CBc5
        Amount/TokenID: 7
        To: 0xff9f4a4Fc82A803bD00052Ed5b90366c8cDa622b
        Submitted with hash 0x49f1a167d0362a0a3f8caa4052697bbd64fd41fbac0e39c4a4044db719b7c26c on the chain Goerli
-----------------------------------------------------------
No token transfers defined for chain Rinkeby
-----------------------------------------------------------

Suggestion:

A large number of transactions that are needed to migrate all ERC721 tokens could be avoided by deploying a migration contract that would iterate over a set of ERC721 tokens (defined by tokenID) and call the adminWithdraw function for each token.

3) Clean up

To fully finalize migration some cleanup should be done, below you can find some suggested steps that should be executed.

Important note: After the below steps are executed, migration will become irreversible so it is very important to properly test that migration was successful before executing clean up steps.

Clean up steps

a) Remove all relayers from the bridge contract

As the v1 bridge will no longer be used, it is strongly suggested to remove all relayers that are registered on the bridge contract. This can be done by invoking function adminRemoveRelayer(address relayerAddress) on the Bridge contract.

b) Revoke all mint roles from old handlers

You can accomplish this by invoking revokeRole(bytes32 role, address account) for each handler on token contracts. So, for example, if you have an ERC20 mint/burn token that is being bridged, you should invoke revokeRole with the handler address for this token and bytes representing MINTER_ROLE defined as keccak256("MINTER_ROLE").

c) Renounce admin role on the bridge contract

The final step should be renouncing admin rights on the bridge contract, as this would disable the v1 bridge contract from being used in the future. This can be done by invoking function renounceAdmin(address newAdmin)

Summary

If all suggested steps were executed correctly, the v1 bridge contract should now be paused, all relayers should be removed from the contract, and admin rights have also been renounced. These steps are important to prevent the exploitation of the v1 bridge contract.

Scripts

You can use small pre-made scripts to facilitate some steps of the migration, as described in the guide itself. Here you can find a repository with these scripts.

After you have provided a valid configuration (see more details about this inside the README file), running scripts is pretty straightforward:

make stop-bridge or make transfer-tokens