Omnichain Governance Executor extends existing governance protocols (e.g., Compound GovernorBravo) with Layer Zero omnichain messaging protocol to enable cross-chain proposal execution.
The two main contracts are OmnichainProposalSender
and OmnichainGovernanceExecutor
.
OmnichainProposalSender
allows sending proposal actions to other chains for execution. It has to be deployed on the main chain where voting is taking place (e.g., Ethereum). When used with GovernorBravo
the owner of the OmnichainProposalSender
must be set to Timelock
contract.
OmnichainGovernanceExecutor
executes the proposal actions on other chains. It must be deployed on each chain that needs to support the remote proposal execution. OmnichainGovernanceExecutor
contract derives from NonBlockingLzApp and provides non-blocking message delivery, meaning failed messages won't block the message queue from the main chain. For the blocking behavior inherit the contract from LzApp.
- Clone the repository
- run
yarn
yarn test
Run the full suite of unit tests.
yarn coverage
Get the coverage report.
- Refer to LayerZero gitbook for LayerZero Endpoint addresses and chain ids.
- Deploy
OmnichainProposalSender
on the main chain (e.g. Ethereum) where voting is taking place. - Deploy
OmnichainGovernanceExecutor
on each chain that needs to support remote proposal execution (e.g., Polygon, Arbitrum, Optimism, etc). - Call
setTrustedRemoteAddress
inOmnichainProposalSender
for each remote chain providing LayerZero chain id and an address ofOmnichainGovernanceExecutor
contract on that chain. - Call
setTrustedRemoteAddress
inOmnichainGovernanceExecutor
on each remote chain proving the main chain id and an address ofOmnichainProposalSender
- Optionally set a custom configuration (e.g. Oracle, Relayer, number of confirmations) by calling
setConfig
inOmnichainProposalSender
and allOmnichainGovernanceExecutor
. - Transfer ownership of
OmnichainProposalSender
to a contract that executes a proposal. ForGovernorBravo
it'sTimelock
contract. - Transfer ownership of each
OmnichainGovernanceExecutor
to itself or aTimelock
contract.
For each remote chain do the following:
- Encode the proposal actions that needs to be executed as follows
payload = abi.encode(targets, values, signatures, calldatas)
- Call
estimateFees
inOmnichainProposalSender
passing the remote chain id, the payload constructed in the previous step and adapter parameters. Adapter parameters allows specifying the amount of gas required on destination to execute the proposal. We recommend testing the execution logic on the destination chain to determine the required amount. To learn more about adapter parameters refer to LayerZero gitbook - Create a proposal action as follows:
target
an address ofOmnichainProposalSender
contractvalue
LayerZero fee for message delivery. Generally it should be equal tonativeFee
obtained in the previous step by callingestimateFees
. However, since there is a significant time period between the proposal creation and execution, the fees estimation might be inaccurate and we're highly recommend increasing the amount of fees passed (e.g.,nativeFee.mul(2)
). Unused amount will be refunded to the address that initiated the proposal execution on the main chain (tx.origin
)signature
the signature ofexecute
function inOmnichainProposalSender
contract ("execute(uint16,bytes,bytes)"
)calldata
the encoded parameters ofexecute
function inOmnichainProposalSender
(abi.encode(remoteExecutorChainId, payload, adapterParams)
)
- Pass the values as part of the corresponding arrays in
propose
function ofGovernorBravoDelegate
contract
When Timelock
contract executes the proposal it calls execute
function in OmnichainProposalSender
which sends the proposal actions to the remote chain.
Sending a message might fail on the main chain the calling execute
. The most common reason for this is insufficient fees supplied.
To retry a failed message do the following:
- Find
StorePayload
event that was emitted. - If the reason indicates insufficient fees, call
estimateFees
again - Call
retryExecute
inOmnichainProposalSender
supplying the parameters from the event and the fees obtained in the previous step
Chain | Contract | Address |
---|---|---|
Ethereum | OmnichainProposalSender | 0xeb0BCF27D1Fb4b25e708fBB815c421Aeb51eA9fc |
Avalanche | OmnichainGovernanceExecutor | 0xeb0BCF27D1Fb4b25e708fBB815c421Aeb51eA9fc |