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.
- 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 fromMultiBridgeSender
. - 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()
ofMultiBridgeReceiver
for each valid message. - Renounce any ownership or special roles of the adapter contracts after initial setup.
- On the source chain, the sender adapter should only accept
- 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.
- dApp contract (
Caller
) on the source chain adds the new Bridge4 receiver adapter toMultiBridgeReceiver
on the destination chain by calling theremoteCall()
function ofMultiBridgeSender
, with arguments to callupdateReceiverAdapter()
of theMultiBridgeReceiver
on the destination chain. - dApp contract (
Caller
) on the source chain adds the new Bridge4 sender adapter toMultiBridgeSender
on the source chain by calling theaddSenderAdapters()
function ofMultiBridgeSender
.
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 withCaller
's address, tx link. - Deploy
MultiBridgeReceiver
on BSC Testnet, tx link. - Deploy
CelerSenderAdapter
on Avalanche Fuji, tx link. SetmultiBridgeSender
, tx link. - Deploy
CelerReceiverAdapter
on BSC Testnet, tx link. SetmultiBridgeReceiver
, tx link. - Register
CelerReceiverAdapter
inCelerSenderAdapter
, tx link. - Register
CelerSenderAdapter
inCelerReceiverAdapter
, tx link. - Renounce owner of
CelerSenderAdapter
andCelerReceiverAdapter
. Not actually did in this example for easier debugging. - Deployer of
MultiBridgeReceiver
initialize this contact, registerCelerReceiverAdapter
with power10000
, and setquorumThreshold
to100
, tx link. Caller
registerCelerSenderAdapter
inMultiBridgeSender
, 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. SetmultiBridgeSender
, 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. SetmultiBridgeReceiver
, tx link. - Register
WormholeReceiverAdapter
inWormholeSenderAdapter
, tx link. - Register
WormholeSenderAdapter
inWormholeReceiverAdapter
, tx link. - Renounce owner of
WormholeSenderAdapter
andWormholeReceiverAdapter
. Not actually did in this example for easier debugging. Caller
make a call toremoteCall()
ofMultiBridgeSender
with calldata callingupdateReceiverAdapter()
ofMultiBridgeReceiver
, in order to registerWormholeReceiverAdapter
inMultiBridgeReceiver
with power10000
, tx link.MultiBridgeReceiver
receive message fromCelerReceiverAdapter
and registerWormholeReceiverAdapter
with power10000
, tx link.Caller
registerWormholeSenderAdapter
inMultiBridgeSender
, 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
.