Multichain Governance
System Overview
The multichain governance system is designed to facilitate the execution of VIP across multiple blockchain networks, integrating with the Access Control Manager (ACM) and LayerZero communication protocol. It extends the governance model proposed by LayerZero.
Key features
VIP Types and Delays:
Provides three VIP options: Normal, Fast-track, and Critical.
Delays can be configured prior to remote execution on destination networks.
Normal VIPs have the greatest delays, whereas Critical VIPs have the smallest delays, indicating their urgency and importance.
Inter-chain communication:
LayerZero provides secure and reliable cross-chain messaging for remote execution commands.
Bridging:
Works with a bridge solution to deliver messages to destination networks.
Enables smooth interoperability between the BNB Chain and other supported networks.
Bridge configurations are flexible to accommodate various networks.
Guardian account:
Authorized by the ACM to revoke orders before they are executed on the target network.
Serves as a fail-safe mechanism, preventing unauthorized or incorrect commands from being performed.
Command limits and pausing:
Allows you to establish daily command restrictions for destination networks.
Adds pause/resume functionality for execution to temporarily halt operations in case of an emergency.
Detailed breakdown
Proposing and Voting:
Proposers send VIPs with BNB Chain commands and remote commands.
Voting takes place on the BNB Chain utilizing existing governance contracts.
Proposals are validated and approved according to predetermined criteria and threshold.
Remote Execution Flow:
Commands for destination networks generate a "Remote VIPs payload”
The payload is routed to the destination network using the bridge solution.
Delay Mechanism:
Remote execution has two delays: bridge delay and executor delay.
Bridge delay is the time it takes for the bridge to propagate a message to the target network, which is commonly measured in minutes.
Executor delay is the duration between the message's arrival on the target network and its execution, which is customizable dependent on VIP type (Normal, Fast-track, or Critical)
Execution and Expiry:
User-triggered execution occurs once both delays have passed, signaling the destination network's readiness to perform the orders via Timelock.
The Guardian account can cancel orders before they are executed, offering a safeguard against malicious or erroneous acts.
"Remote VIPs" become "Expired" if no execution happens within a set grace period, avoiding stale or outdated commands from being executed.
Command Restrictions:
VIPs can only include one set of commands per destination network to prevent duplication and conflicts.
Duplicate commands for the same network within a VIP are not permitted, ensuring consistent and reliable execution.
Executor-Side Features:
Sets a daily limit on the number of commands received per network.
Implements pause/resume functionality for the execute function in the target governance contract, enabling administrators to manage the system's operational state effectively.
Detailed overview of remote proposal execution (step-by-step)
Proposing a remote VIP on BNB Chain
A proposer submits a VIP through the existing governance mechanism on the BNB Chain.
The must VIP include a command invoking the
OmnichainProposalSender::execute
function which will send the remote VIP payload.The
execute
function takes four arguments:chainID
: Identifies the destination network for the remote execution (endpointId
according to LayerZero).payload
: Encoded data (off-chain) containing the specific commands to be executed on the target network.adapterParams
: The params used to specify the custom amount of gas required for the execution on the destination encoded as (ethers.utils.solidityPack(['uint16','uint256'],[1, gasValue])).zroPaymentAddress_
: The address of the ZRO token holder who would pay for the transaction.
Eligibility checks and limits
Before sending the remote execution message, the system verifies eligibility based on predefined thresholds and limits. These restrictions ensure responsible resource allocation and prevent potential misuse.
Remote proposal ID generation
It's crucial to understand that the proposal ID for the remote execution on the destination network differs from the initial VIP ID proposed on the BNB Chain. This remote proposal ID starts from 1.
Message relay based on outcome
Success: If the eligibility checks and limits are met, the encoded message (payload) is relayed across chains using the LayerZero bridge.
Failure: If the checks fail, the system handles the situation differently based on the cause:
Insufficient Gas: The
retryExecute
function is used to attempt redelivering the message with potentially adjusted gas fees.Logical Error or Check Failure: The
fallbackWithdraw
function removes the message from the queue, preventing further retries if the failure stems from inherent logic errors or failed eligibility checks.
Receiving and Queuing on the Destination Network
Upon successful reception by the destination network's executor contract (
OmnichainGovernanceExecutor
), the remote proposal enters a "Queued" state.This queuing process applies additional eligibility checks specific to the receiving network, ensuring compliance with its governance rules and thresholds of commands limits.
Delay mechanism and execution
Once the configured delay for the remote proposal type (Normal, Fast-track, Critical) elapses, the proposal becomes eligible for execution.
Any user can then trigger the execution of the queued commands on the destination network.
Ownership and Access Control
OmnichainProposalSender
(BNB Chain):Owned by: NormalTimelock contract on the BNB Chain.
Authorized callers: Timelocks (Normal, Critical, Fast-track) are authorized to call the
execute
function on this contract.
OmnichainGovernanceExecutor
(destination network):Owned by:
OmnichainExecutorOwner
contract. This owner performs Access Control Manager (ACM) checks before allowing any function calls on this contract.
TimelockV8
:Owned by:
OmnichainGovernanceExecutor
contract. This ownership grantsTimelockV8
the authority to perform specific actions like queuing, canceling, and executing remote proposals.
Potential Failures and Retry Options
If a VIP fails to send the proposal from source chain (BNB Chain) to destination network, the message will be saved and can be retried with the
retryExecute
function. This mechanism allows for the redelivery of the message with potentially adjusted gas fees to ensure successful execution.In case where the VIP fails to send the proposal from source chain (BNB Chain) to destination network due to non-retryable conditions and funds are stuck in the
OmnichainProposalSender
contract, funds can be withdrawn by invoking thefallbackWithdraw
function. This ensures that funds are not permanently locked in the contract due to failed execution attempts.If a proposal's queueing fails on the destination network due to insufficient gas, the message will be saved and can be retried with the
retryMessage
function.If a proposal's queueing fails on the destination network due to reasons such as reaching caps or other constraints, the message will be saved and can be retried with the
retryMessage
function. This provides flexibility in addressing various failure scenarios and ensures that execution attempts are made until successful.If a proposal's queueing fails on the destination network because the contract has been paused, the message will be saved and can be retried with the
retryMessage
function after the contract has been unpaused.
Contracts overview
Contract 1: BaseOmnichainControllerSrc
BaseOmnichainControllerSrc
Functionality
This contract serves as the framework for secure omnichain (cross-chain) communication. Its primary responsibilities include managing daily command limits and enabling pausing of controlled message transmission across chains.
Key features
Access Control Integration: Integrates an
AccessControlManager
contract to enforce access control for critical functions. This ensures that only authorised entities can execute commands, enhancing security.Daily Command Limits: Establishes a per-chain limit on the number of commands that can be sent within a 24-hour window. This feature prevents potential abuse and maintains system stability.
Pausable: Implements a pausing mechanism, inherited from the OpenZeppelin
Pausable
contract. This functionality allows the contract owner to temporarily halt omnichain communication if necessary.
Architecture
Inheritance: Extends the functionalities of the
Ownable
andPausable
contracts from OpenZeppelin, inheriting ownership management and pausing capabilities.
State variables
accessControlManager (address)
: Stores the address of theAccessControlManager
contract.chainIdToMaxDailyLimit (mapping)
: Maps chain IDs to their corresponding daily command limits.chainIdToLast24HourCommandsSent (mapping)
: Tracks the number of commands sent within the last 24 hours for each chain.chainIdToLast24HourWindowStart (mapping)
: Records the timestamp when the last 24-hour window for a chain began.chainIdToLastProposalSentTimestamp (mapping)
: maintains the timestamp of the last proposal sent to a specific chain to prevent sending multiple proposals within the same block.
Events
SetMaxDailyLimit (event)
: Emitted when the daily command limit for a chain is modified.NewAccessControlManager (event)
: Triggered when the address of theAccessControlManager
is updated.
Functions
constructor(address accessControlManager_)
: Initializes the contract with the address of theAccessControlManager
.setMaxDailyLimit(uint16 chainId_, uint256 limit_)
: Sets the maximum daily command limit for a specific chain ID. Requires permission from theAccessControlManager
.pause()
: Triggers the paused state, halting omnichain communication. RequiresAccessControlManager
permission.unpause()
: Resumes omnichain communication from the paused state. RequiresAccessControlManager
permission.setAccessControlManager(address accessControlManager_)
: Updates the address of theAccessControlManager
contract. Only callable by the contract owner.isEligibleToSend(uint16 dstChainId, uint256 noOfCommands_)
: Checks if sending the specified number of commands to the given chain is permissible based on daily limits and time windows.ensureAllowed(string memory functionSig)
: Ensures the caller has permission to execute a specific function, leveraging theAccessControlManager
.
Contract 2: OmnichainProposalSender
OmnichainProposalSender
Functionality
This contract facilitates cross-chain message transmission triggered by governor proposals on the main (BNB) chain. It sends proposal execution data to designated remote chains for processing.
Key features
LayerZero Integration: Utilises the LayerZero communication protocol for efficient and reliable cross-chain message delivery.
Remote Chain Management: Allows defining trusted remote contracts (receivers) on other chains using
setTrustedRemoteAddress
.Failed Message Handling: Stores the execution hashes of failed messages to facilitate resending or clearing them in case of insufficient fees or other issues.
Security Measures: Enforces access control using the
AccessControlManager
for critical functions.
Architecture
Inheritance: Inherits functionalities from both the
ReentrancyGuard
andBaseOmnichainControllerSrc
contracts, providing reentrancy protection and foundational omnichain communication capabilities.
State variables
proposalCount (uint256)
: Tracks the total number of remote proposals.storedExecutionHashes (mapping)
: Stores the execution hashes of failed messages for retry or clearing purposes.LZ_ENDPOINT (ILayerZeroEndpoint)
: Interface for interacting with the LayerZero communication protocol.trustedRemoteLookup (mapping)
: Maps remote chain IDs to trusted remote contract addresses for message transmission.
Events
SetTrustedRemoteAddress (event)
: Triggered when a trusted remote address is set for a remote chain.TrustedRemoteRemoved (event)
: Emitted when a trusted remote address is removed from storage.ExecuteRemoteProposal (event)
: Indicates the execution of a proposal on a remote chain.ClearPayload (event)
: Signals the successful clearing of a previously failed message.StorePayload (event)
: Records the storage of an execution hash for a failed message, along with relevant details.FallbackWithdraw (event)
: Indicates a fallback withdrawal of funds in case of failed messages.
Functions
constructor(ILayerZeroEndpoint lzEndpoint_, address accessControlManager_)
: Initializes the contract with the LayerZero endpoint and the address of theAccessControlManager
.estimateFees(uint16 remoteChainId_, bytes calldata payload_, bytes calldata adapterParams_)
: Estimates LayerZero fees for cross-chain message delivery based on payload and adapter parameters.execute(uint16 remoteChainId_, bytes calldata payload_, bytes calldata adapterParams_)
: Sends a message to execute a remote proposal, storing execution hashes if the message fails.retryExecute(...)
: Resends a previously failed message with potentially additional fees, ensuring reentrancy protection.fallbackWithdraw(...)
: Allows the owner to withdraw funds in case of failed messages.setTrustedRemoteAddress(uint16 remoteChainId_, bytes calldata newRemoteAddress_)
: Sets the remote message receiver address for a specified remote chain, requiringAccessControlManager
permission.setConfig(uint16 version_, uint16 chainId_, uint256 configType_, bytes calldata config_)
: Sets the configuration of the LayerZero messaging library, controlled by theAccessControlManager
.setSendVersion(uint16 version_)
: Sets the messaging library version, with permission from theAccessControlManager
.getConfig(uint16 version_, uint16 chainId_, uint256 configType_)
: Retrieves the configuration of the LayerZero messaging library.
Contract 3: BaseOmnichainControllerDest
BaseOmnichainControllerDest
Functionality
This contract serves as the base for the Omnichain controller destination contract. It provides functionality related to daily command limits and pausing.
State variables
maxDailyReceiveLimit (uint256)
: Maximum daily limit for receiving commands from BNB Chain.last24HourCommandsReceived (uint256)
: Total received commands within the last 24-hour window from BNB Chain.last24HourReceiveWindowStart (uint256)
: Timestamp when the last 24-hour window started from BNB Chain.
Events
SetMaxDailyReceiveLimit (event)
: Emitted when the maximum daily limit for receiving commands from BNB Chain is modified.
Functions
constructor(address endpoint_)
: Initializes the contract with the LayerZero endpoint address.setMaxDailyReceiveLimit(uint256 limit_)
: Sets the maximum daily limit for receiving commands. Only callable by the contract owner.pause()
: Triggers the paused state of the controller. Only callable by the contract owner.unpause()
: Triggers the resume state of the controller. Only callable by the contract owner.renounceOwnership()
: Overrides the renounceOwnership function to prevent accidental renouncement of ownership._isEligibleToReceive(uint256 noOfCommands_)
: Checks the eligibility to receive commands based on the daily limit and updates the state accordingly.
Contract 4: OmnichainGovernanceExecutor
OmnichainGovernanceExecutor
Functionality
This contract executes proposal transactions sent from the main chain. It controls LayerZero configuration and implements a non-blocking behavior.
State variables
GUARDIAN (address)
: A privileged role that can cancel any proposal.srcChainId (uint16)
: Stores the layerzero endpoint ID.lastProposalReceived (uint256)
: Last proposal count received.proposals (mapping)
: Official record of all proposals ever proposed.proposalTimelocks (mapping)
: Mapping containing Timelock addresses for each proposal type.queued (mapping)
: Represents the queue state of a proposal.
Events
ProposalReceived (event)
: Emitted when a proposal is received.ProposalQueued (event)
: Emitted when a proposal is queued.ProposalExecuted (event)
: Emitted when a proposal is executed.ReceivePayloadFailed (event)
: Emitted when a payload receive fails.ProposalCanceled (event)
: Emitted when a proposal is canceled.TimelockAdded (event)
: Emitted when a Timelock is added.SetSrcChainId (event)
: Emitted when the source layer zero endpoint ID is updated.SetTimelockPendingAdmin (event)
: Emitted when pending admin of Timelock is updated.NewGuardian (event)
: Emitted when guardian of OmnichainGovernanceExecutor is updated.
Functions
constructor(address endpoint_, address guardian_, uint16 srcChainId_)
: Initialises the contract with the LayerZero endpoint address, guardian address, and source chain ID.setSrcChainId(uint16 srcChainId_)
: Updates the source layerzero endpoint ID. Only callable by the contract owner.addTimelocks(ITimelock[] memory timelocks_)
: Adds Timelocks to the ProposalTimelocks mapping. Only callable by the contract owner.execute(uint256 proposalId_)
: Executes a queued proposal if the ETA has passed.cancel(uint256 proposalId_)
: Cancels a proposal if the sender is the guardian and the proposal is not executed.state(uint256 proposalId_)
: Gets the state of a proposal._blockingLzReceive(...)
and_nonblockingLzReceive(...)
: Process LayerZero receive requests, with blocking and non-blocking behaviour respectively._queue(uint256 proposalId_)
: Queues a proposal for execution._queueOrRevertInternal(...)
: Checks for a unique proposal and queues it or reverts if already queued.setTimelockPendingAdmin(address pendingAdmin_, uint8 proposalType_)
: Sets the new pending admin of the Timelock.setGuardian(address newGuardian)
: Sets the new guardian of the OmnichainGovernanceExecutor.
Contract 5: OmnichainExecutorOwner
OmnichainExecutorOwner
Functionality
The OmnichainExecutorOwner contract serves as a governance and access control mechanism for managing the OmnichainGovernanceExecutor
contract. It allows the owner to control the functions that can be executed on the OmnichainGovernanceExecutor
contract, upsert function signatures into a registry, and transfer ownership of the OmnichainGovernanceExecutor
contract.
State variables
OMNICHAIN_GOVERNANCE_EXECUTOR (immutable)
:This variable holds the address of the
OmnichainGovernanceExecutor
contract and is immutable once initialized.
functionRegistry (mapping)
:This mapping stores the function signatures along with their corresponding 4-byte hash values.
It allows the contract owner to register which functions are permitted to be executed on the
OmnichainGovernanceExecutor
contract.
Events
FunctionRegistryChanged (event)
:This event is emitted when a function is added or removed from the function registry.
It provides information about the function signature and whether it is active (added) or inactive (removed).
Functions
constructor(address omnichainGovernanceExecutor_)
:Initializes the contract with the address of the
OmnichainGovernanceExecutor
contract.This constructor ensures that the provided address is not zero.
initialize(address accessControlManager_)
:Initializes the contract with the address of the access control manager.
This function sets up access control for the contract.
fallback(bytes calldata data_)
:Acts as a fallback function that is invoked when a called function does not exist in the contract.
It checks if the function signature exists in the function registry and if the caller has permission to execute it.
Then it forwards the function call to the
OmnichainGovernanceExecutor
contract.If the call fails, it reverts with an error message.
upsertSignature(string[] calldata signatures_, bool[] calldata active_)
:Allows the owner to upsert (add or remove) function signatures into the function registry.
The owner can specify whether each function signature should be added (
true
) or removed (false
).This function ensures that input arrays have the same length.
It emits the
FunctionRegistryChanged
event for each function signature that is added or removed.
transferBridgeOwnership(address newOwner_)
:Transfers the ownership of the
OmnichainGovernanceExecutor
contract from this contract to a new owner.This function is controlled by the access control manager to ensure that only authorized entities can transfer ownership.
It ensures that the new owner address is not zero before transferring ownership.
renounceOwnership()
:Overrides the
renounceOwnership
function from the parent contract to provide an empty implementation.This prevents accidental renouncement of ownership, as ownership renouncement is handled through the
transferBridgeOwnership
function.
Contract 6: TimelockV8
TimelockV8
Functionality
The TimelockV8
contract is a Solidity V8 implementation of a timelock mechanism designed to execute transactions with a delay. It incorporates additional features such as setting a delay period, accepting pending admins, and queuing, executing, and canceling transactions. This contract ensures that specific transactions can only be executed after a predefined period, enhancing security and providing governance control.
Key features
Delayed Transaction Execution: Allows transactions to be queued with an associated delay period before execution.
Admin Management: Supports the transition of admin roles by accepting pending admin proposals.
Transaction Queueing: Provides functionality to queue transactions for future execution, ensuring timely processing.
Transaction Execution: Executes queued transactions after the specified delay period has elapsed, subject to admin authorisation.
Transaction Cancellation: Allows cancellation of queued transactions before execution, providing flexibility and control.
Events
NewAdmin (event)
: Signals the acceptance of a new admin account.NewPendingAdmin (event)
: Indicates the proposal of a new admin account.NewDelay (event)
: Notifies when the delay period for transaction execution is updated.CancelTransaction (event)
: Broadcasts the cancellation of a queued transaction.ExecuteTransaction (event)
: Broadcasts the execution of a queued transaction.QueueTransaction (event)
: Broadcasts the queuing of a new transaction for future execution.
Constants
DEFAULT_GRACE_PERIOD
: Default grace period for executing queued transactions (14 days).DEFAULT_MINIMUM_DELAY
: Default minimum delay period for queuing transactions (1 hour).DEFAULT_MAXIMUM_DELAY
: Default maximum delay period for queuing transactions (30 days).
State variables
admin (address)
: Stores the address of the current admin authorised to manage the timelock.pendingAdmin (address)
: Stores the address of the proposed admin awaiting acceptance.delay (uint256)
: Specifies the delay period for executing queued transactions.
Functions
setDelay(uint256 delay_)
: Allows the admin to set the delay period for transaction execution.acceptAdmin()
: Enables the pending admin to accept the admin role.setPendingAdmin(address pendingAdmin_)
: Allows to propose a new admin account via proposal or admin.queueTransaction(...)
: Queues a transaction for future execution after a specified delay.cancelTransaction(...)
: Cancels a queued transaction before execution.executeTransaction(...)
: Executes a queued transaction after the delay period has elapsed.
Last updated