A Multi-bridge Implementation for Uniswap Governance is Now Available
It is now quite clear that the initial concern raised by the community about Celer’s upgradability contract, which we addressed here and plan to deprecation soon, is absolutely not bigger concerns comparing to many of the issues revealed and discussed in the forum.
While we have a lot to say about the ongoing debates in the forum and CT, we refrain from doing so and instead now deliver a multi-bridge Uniswap governance solution that is vendor-lock-in-free for Uniswap community to consider.
For Uniswap community who has not casted votes yet, please vote for Celer to express your support for this kind of multi-bridge solution.
This solution is strictly better than choosing a single bridge in terms of security and availability. In addition, it also retains the strongest bargaining power in the hands of Uniswap community to always receive the best services. See this for more context.
The implementation combines both Celer and Wormhole and is open-sourced here
All other bridges/cross-chain solutions such as LayerZero can be added easily via adapters.
We are setting up a testnet transaction to demonstrate this end-to-end. @Wormhole team we hope that you can help to set up a relayer for this because otherwise the only easy way would be deploying on Avalanche and BSC testnet where Wormhole generic relayers are available to our knowledge. Feel free to DM!
Now let’s get to the solution specification.
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 are 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 are 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. This would be the use case for Uniswap governance contract on Ethereum
calling remote functions of contracts on other EVM chains.
Workflow
Send message on source chain
To send a message to execute a remote call on the destintion chain, sender on the source chain should call remoteCall()
of MultiBridgeSender
, which invokes sendMessage()
of every bridge sender apdater to send messages via different message bridges.
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Source chain │
│ │
│ ┌─────────────────┐ ┌───────────────────┐ │
│ ┌──►│ Bridge1 Adapter ├──►│ Bridge1 Contracts │ │
│ │ └─────────────────┘ └───────────────────┘ │
│ │ │
│ ┌────────┐remoteCall()┌───────────────────┐sendMessage()│ ┌─────────────────┐ ┌───────────────────┐ │
│ │ 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 decode 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 the a threshold, which means enough number of 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 contracts, or call the param adjustment functions of the MultiBridgeReceiver
itself. Note that the only legit message sender is the trusted dApp contract on the source chain, which means only that single dApp contract has the ability to execute functions calls through the MultiBridgeReceiver
contracts on different other 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 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 inital parameter setup.
- On the source chain, the sender adapter should only accept
- Bridge4 provider deploy and open source the adapter contracts. 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 sender adapter toMultiBridgeSender
on the source chain by calling theaddSenderAdapters()
function ofMultiBridgeSender
. - 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.
Updating quorum threshold is similar to configure a new bridge receiver adapter on destination chain. It requires a remoteCall()
from the source chain Caller
with calldata calling updateQuorumThreshold()
of the MultiBridgeReceiver
on the destination chain.
Example
Use case: contract A on Goerli send message to contract B on BSC Testnet in order to call enableFeeAmount()
for state change. Apply a 2-of-3 messages governance model with message bridge C, D and E.
Deployment and initialization
- Deploy
MultiBridgeSender
on Goerli, set address of A as allowed caller. - Deploy
MultiBridgeReceiver
on BSC Testnet. - Each message bridge provider prepare their own
SenderAdapter
andReceiverAdapter
, named with a prefix of their bridge name. Take preparation ofCSenderAdapter
andCReceiverAdapter
as an example.- Deploy
CSenderAdapter
on Goerli, set address ofMultiBridgeSender
as multiBridgeSender. - Deploy
CReceiverAdapter
on BSC Testnet, set address ofMultiBridgeReceiver
as multiBridgeReceiver. - Call updateReceiverAdapter() of
CSenderAdapter
, set address ofCReceiverAdapter
on BSC Testnet(chain id 97) as a valid ReceiverAdapter. - Call updateSenderAdapter() of
CReceiverAdapter
, set address ofCSenderAdapter
on Goerli(chain id 5) as a valid SenderAdapter. - Transfer ownership of
CSenderAdapter
andCReceiverAdapter
to address(0).
- Deploy
- Once all message bridges are ready, somehow let contract A call addSenderAdapters() of
MultiBridgeSender
with an address array ofCSenderAdapter
,DSenderAdapter
andESenderAdapter
. - Call
initialize()
ofMultiBridgeReceiver
, with an address array ofCReceiverAdapter
,DReceiverAdapter
andEReceiverAdapter
, and power threshold 2.
Sending your message
Prepare a calldata for contract B for calling enableFeeAmount()
, then somehow let contract A call remoteCall()
of MultiBridgeSender
with _dstChainId = 97
, _target = <address of contract B>
and _callData = <calldata you prepared>
.
Result
Imagine that the messages sent via C, D and E received by MultiBridgeReceiver
on BSC Testnet in an order of 1.C 2.D 3.E
. During receiving message sent via D, accumulated power reaches power threshold 2, which result in message execution(the calldata will be sent to contract B).
Notes to the community
We are more than happy to answer any questions, help with tests, collaborate with similar-minded builders like @AlexSmirnov, iterate based on feedbacks and provide audits from community selected firms.
Although Celer, being a 2018-vintage project, does not have the backing of large UNI-holding investors at this moment, we do trust the integrity and intelligence of all the Uniswap delegates to choose this positive-sum path that ensures the highest level of security and service quality for Uniswap’s multi-blockchain expansion.
We will update this post with testnet transactions once we worked through the small integration work using @Wormhole .