/multibridge

Send Cross-Chain Messages through Multiple Bridges

Primary LanguageSolidityGNU General Public License v3.0GPL-3.0

Send Cross-Chain Messages through Multiple Bridges

This is a solution for cross-chain message passing without vendor lock-in and with enhanced security beyond any single bridge. A message with multiple copies is sent through different bridges to the destination chains, and will only be executed at the destination chain when the same message has been delivered by a quorum of different bridges.

The current solution is designed for messages being sent from one source chain to multiple destination chains. It also requires that there is only one permitted sender on the source chain. For example, one use case could be a governance contract on Ethereum calling remote functions of contracts on other EVM chains. Each dApp who wants to utilize this framework needs to deploy its own set of contracts.

Workflow

Send message on source chain

To send a message to execute a remote call on the destination chain, sender on the source chain should call remoteCall() of MultiBridgeSender, which invokes sendMessage() of every bridge sender adapter to send messages via different message bridges.

┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ Source chain                                                                              │
│                                                                                           │
│                                               ┌─────────────────┐   ┌───────────────────┐ │
│                                            ┌─►│ Bridge1 Adapter ├──►│ Bridge1 Contracts │ │
│                                            │  └─────────────────┘   └───────────────────┘ │
│        remoteCall          dispatchMessage │                                              │
│ ┌────────┐    ┌───────────────────┐        │  ┌─────────────────┐   ┌───────────────────┐ │
│ │ Caller ├───►│ MultiBridgeSender ├────────┼─►│ Bridge2 Adapter ├──►│ Bridge2 Contracts │ │
│ └────────┘    └───────────────────┘        │  └─────────────────┘   └───────────────────┘ │
│                                            │                                              │
│                                            │  ┌─────────────────┐   ┌───────────────────┐ │
│                                            └─►│ Bridge3 Adapter ├──►│ Bridge3 Contracts │ │
│                                               └─────────────────┘   └───────────────────┘ │
│                                                                                           │
└───────────────────────────────────────────────────────────────────────────────────────────┘

Receive message on destination chain

On the destination chain, MultiBridgeReceiver receives messages from every bridge receiver adapter. Each receiver adapter gets encoded message data from its bridge contracts, and then decodes the message and call receiveMessage() of MultiBrideReceiver.

MultiBridgeReceiver maintains a map from bridge adapter address to its power. Only adapter with non-zero power has access to receiveMessage() function. If the accumulated power of a message has reached a threshold, which means enough different bridges have delivered a same message, the message will be executed by the MultiBrideReceiver contract.

The message execution will invoke a function call according to the message content, which will either call functions of other receiver contracts, or call the param adjustment functions (e.g., add/remove adapter, update threshold) of the MultiBridgeReceiver itself. Note that the only legit message sender is the trusted caller on the source chain, which means only that single source chain caller can trigger function calls of the MultiBridgeReceiver contracts on desitnation chains.

┌────────────────────────────────────────────────────────────────────────────────────────────┐
│ Destination chain                                                                          │
│                                                                                            │
│ ┌───────────────────┐   ┌─────────────────┐                                                │
│ │ Bridge1 Contracts ├──►│ Bridge1 Adapter ├─┐                                              │
│ └───────────────────┘   └─────────────────┘ │                                              │
│                                             │receiveMessage              call              │
│ ┌───────────────────┐   ┌─────────────────┐ │     ┌─────────────────────┐     ┌──────────┐ │
│ │ Bridge1 Contracts ├──►│ Bridge2 Adapter ├─┼────►│ MultiBridgeReceiver ├────►│ Receiver │ │
│ └───────────────────┘   └─────────────────┘ │     └─────────────────────┘     └──────────┘ │
│                                             │                                              │
│ ┌───────────────────┐   ┌─────────────────┐ │                                              │
│ │ Bridge2 Contracts ├──►│ Bridge3 Adapter ├─┘                                              │
│ └───────────────────┘   └─────────────────┘                                                │
│                                                                                            │
└────────────────────────────────────────────────────────────────────────────────────────────┘

Add new bridge and update threshold

Below are steps to add a new bridge (e.g., Bridge4) by the dApp community.

  1. Bridge4 provider should implement and deploy Bridge4 adapters on the source chain and all destination chains. The adapter contracts should meet the following requirements.
    • On the source chain, the sender adapter should only accept sendMessage() call from MultiBridgeSender.
    • On the destination chain, the receiver adapter should only accept messages sent from the Bridge4 sender adapter on the source chain, and then call receiveMessage() of MultiBridgeReceiver for each valid message.
    • Renounce any ownership or special roles of the adapter contracts after initial setup.
  2. Bridge4 provider deploys the adapter contracts and makes them open source. The dApp community should review the code and check if the requirements above are met.
  3. dApp contract (Caller) on the source chain adds the new Bridge4 receiver adapter to MultiBridgeReceiver on the destination chain by calling the remoteCall() function of MultiBridgeSender, with arguments to call updateReceiverAdapter() of the MultiBridgeReceiver on the destination chain.
  4. dApp contract (Caller) on the source chain adds the new Bridge4 sender adapter to MultiBridgeSender on the source chain by calling the addSenderAdapters() function of MultiBridgeSender.

Updating the quorum threshold is similar to configuring a new bridge receiver adapter on destination chains. It requires a remoteCall() from the source chain Caller with calldata calling updateQuorumThreshold() of the MultiBridgeReceiver on the destination chain.

Example

Use case: Caller(address: 0x58b529F9084D7eAA598EB3477Fe36064C5B7bbC1) on Avalanche Fuji send message to UniswapV3Factory on BSC Testnet through Celer and Wormhole, in order to call enableFeeAmount() for state change.

Deployment and initialization

  • Deploy MultiBridgeSender on Avalanche Fuji with Caller's address, tx link.
  • Deploy MultiBridgeReceiver on BSC Testnet, tx link.
  • Deploy CelerSenderAdapter on Avalanche Fuji, tx link. Set multiBridgeSender, tx link.
  • Deploy CelerReceiverAdapter on BSC Testnet, tx link. Set multiBridgeReceiver, tx link.
  • Register CelerReceiverAdapter in CelerSenderAdapter, tx link.
  • Register CelerSenderAdapter in CelerReceiverAdapter, tx link.
  • Renounce owner of CelerSenderAdapter and CelerReceiverAdapter. Not actually did in this example for easier debugging.
  • Deployer of MultiBridgeReceiver initialize this contact, register CelerReceiverAdapter with power 10000, and set quorumThreshold to 100, tx link.
  • Caller register CelerSenderAdapter in MultiBridgeSender, tx link.

Test cross-chain updating quorum threshold through single bridge(Celer)

Caller make a call to remoteCall() of MultiBridgeSender with calldata calling updateQuorumThreshold() of MultiBridgeReceiver, in order to update quorum threshold to 98, tx link.

MultiBridgeReceiver receive message from CelerReceiverAdapter and update quorum threshold to 98, tx link.

Add a new bridge Wormhole

  • Deploy WormholeSenderAdapter on Avalanche Fuji, tx link. Set multiBridgeSender, tx link. Set a chain id map from formal chain id to the one used by Wormhole, tx link.
  • Deploy WormholeReceiverAdapter on BSC Testnet, tx link. Set multiBridgeReceiver, tx link.
  • Register WormholeReceiverAdapter in WormholeSenderAdapter, tx link.
  • Register WormholeSenderAdapter in WormholeReceiverAdapter, tx link.
  • Renounce owner of WormholeSenderAdapter and WormholeReceiverAdapter. Not actually did in this example for easier debugging.
  • Caller make a call to remoteCall() of MultiBridgeSender with calldata calling updateReceiverAdapter() of MultiBridgeReceiver, in order to register WormholeReceiverAdapter in MultiBridgeReceiver with power 10000, tx link.
  • MultiBridgeReceiver receive message from CelerReceiverAdapter and registerWormholeReceiverAdapter with power 10000, tx link.
  • Caller register WormholeSenderAdapter in MultiBridgeSender, tx link.

Test cross-chain updating quorum threshold through two bridges(Celer&Wormhole)

Caller make a call to remoteCall() of MultiBridgeSender with calldata calling updateQuorumThreshold() of MultiBridgeReceiver, in order to update quorum threshold to 96, tx link.

MultiBridgeReceiver receive message from WormholeReceiverAdapter, tx link. Accumulated power of this message is 10000/20000 and not reaches quorum threshold 98/100.

MultiBridgeReceiver receive message from CelerReceiverAdapter, tx link. Accumulated power of this message changes to 20000/20000 and reaches quorum threshold 98/100. Then within the same tx, quorum threshold is updated to 96.

Test cross-chain calling enableFeeAmount() of UniswapV3Factory

UniswapV3Factory for testing is deployed on BSC Testnet, tx link.

Transfer owner of UniswapV3Factory to MultiBridgeReceiver, because enableFeeAmount() is only callable from owner. tx link.

Caller make a call to remoteCall() of MultiBridgeSender with calldata calling enableFeeAmount() of UniswapV3Factory, in order to update feeAmountTickSpacing[40] = 40, tx link.

MultiBridgeReceiver receive message from WormholeReceiverAdapter, tx link. Accumulated power of this message is 10000/20000 and not reaches quorum threshold 96/100.

MultiBridgeReceiver receive message from CelerReceiverAdapter, tx link. Accumulated power of this message changes to 20000/20000 and reaches quorum threshold 96/100. Then within the same tx, MultiBridgeReceiver invoke enableFeeAmount() of UniswapV3Factory and set feeAmountTickSpacing[40] = 40.