Skip to content

Broadcaster

Once a governance vote on Ethereum is successfully passed and executed, a corresponding sequence of messages needs to be communicated to other chains. The Broadcaster contract is responsible for broadcasting messages from Ethereum to the Relayer contract on other chains.

Broadcaster.vy

Because L2's provide different infrastructures to broadcast messages, the individual broadcaster contracts might slightly vary in their source code and vyper version.

The following is a list of the individual broadcaster contracts:

A comprehensive list of all deployed contracts is available here .

The Broadcaster contracts are managed by the following three admins, which are controlled by the DAO:

Upgradable Ownership

The admins of the Broadcaster contracts are upgradable via a commit-apply process after a governance vote has passed.


Optimism and Optimistic Rollups

broadcast

OptimismBroadcaster.broadcast(_messages: DynArray[Message, MAX_MESSAGES], _gas_limit: uint32 = 0)

Guarded Method

This function is only callable by one of the agents (ownership, parameter or emergency).

Function to broadcast a sequence of messages to the Relayer contract on a L2.

Input Type Description
_messages DynArray[Message, MAX_MESSAGES] Sequence of messages to broadcast
_gas_limit uint256 Gas limit for execution on L2; Defaults to 0
Source code
enum Agent:
    OWNERSHIP
    PARAMETER
    EMERGENCY

struct Message:
    target: address
    data: Bytes[MAX_BYTES]

MAX_BYTES: constant(uint256) = 1024
MAX_MESSAGES: constant(uint256) = 8

agent: HashMap[address, Agent]

ovm_chain: public(address)  # CanonicalTransactionChain
ovm_messenger: public(address)  # CrossDomainMessenger

@external
def broadcast(_messages: DynArray[Message, MAX_MESSAGES], _gas_limit: uint32 = 0):
    """
    @notice Broadcast a sequence of messeages.
    @param _messages The sequence of messages to broadcast.
    @param _gas_limit The L2 gas limit required to execute the sequence of messages.
    """
    agent: Agent = self.agent[msg.sender]
    assert agent != empty(Agent)

    # https://community.optimism.io/docs/developers/bridge/messaging/#for-l1-%E2%87%92-l2-transactions
    gas_limit: uint32 = _gas_limit
    if gas_limit == 0:
        gas_limit = OVMChain(self.ovm_chain).enqueueL2GasPrepaid()

    raw_call(
        self.ovm_messenger,
        _abi_encode(  # sendMessage(address,bytes,uint32)
            self,
            _abi_encode(  # relay(uint256,(address,bytes)[])
                agent,
                _messages,
                method_id=method_id("relay(uint256,(address,bytes)[])"),
            ),
            gas_limit,
            method_id=method_id("sendMessage(address,bytes,uint32)"),
        ),
    )
>>> soon

ovm_chain

OptimismBroadcaster.ovm_chain() -> address: view

Getter for the OVM Canonical Transaction Chain contract. This contract can be changed using the set_ovm_chain function.

Source code
interface OVMChain:
    def enqueueL2GasPrepaid() -> uint32: view

ovm_chain: public(address)  # CanonicalTransactionChain
>>> OptimismBroadcaster.ovm_chain()
'0x5E4e65926BA27467555EB562121fac00D24E9dD2'

ovm_messenger

OptimismBroadcaster.ovm_messenger(): view

Getter for the OVM Cross Domain Messenger contract. This contract can be changed using the set_ovm_messenger function.

Source code
ovm_messenger: public(address)  # CrossDomainMessenger
>>> OptimismBroadcaster.ovm_messenger()
'0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1'

set_ovm_chain

OptimismBroadcaster.set_ovm_chain(_ovm_chain: address):

Guarded Method

This function can only be called by the ownership admin.

Function to set a new OVM Canonical Transaction Chain contract.

Emits: SetOVMChain event.

Input Type Description
_ovm_chain address New ovm chain address
Source code
event SetOVMChain:
    ovm_chain: address

struct AdminSet:
    ownership: address
    parameter: address
    emergency: address

admins: public(AdminSet)

@external
def set_ovm_chain(_ovm_chain: address):
    """
    @notice Set the OVM Canonical Transaction Chain storage variable.
    """
    assert msg.sender == self.admins.ownership

    self.ovm_chain = _ovm_chain
    log SetOVMChain(_ovm_chain)

This example sets the ovm_chain from ZERO_ADDRESS to 0x5E4e65926BA27467555EB562121fac00D24E9dD2.

>>> OptimismBroadcaster.ovm_chain()
'0x0000000000000000000000000000000000000000'

>>> OptimismBroadcaster.set_ovm_chain(0x5E4e65926BA27467555EB562121fac00D24E9dD2)

>>> OptimismBroadcaster.ovm_chain()
'0x5E4e65926BA27467555EB562121fac00D24E9dD2'

set_ovm_messenger

OptimismBroadcaster.set_ovm_messenger):

Guarded Method

This function can only be called by the ownership admin.

Function to set a new OVM Cross Domain messenger contract.

Emits: SetOVMMessenger event.

Input Type Description
_ovm_messenger address New ovm messenger address
Source code
event SetOVMMessenger:
    ovm_messenger: address

struct AdminSet:
    ownership: address
    parameter: address
    emergency: address

admins: public(AdminSet)

@external
def set_ovm_messenger(_ovm_messenger: address):
    """
    @notice Set the OVM Cross Domain Messenger storage variable.
    """
    assert msg.sender == self.admins.ownership

    self.ovm_messenger = _ovm_messenger
    log SetOVMMessenger(_ovm_messenger)

This example sets the ovm_messenger from ZERO_ADDRESS to 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1.

>>> OptimismBroadcaster.ovm_messenger()
'0x0000000000000000000000000000000000000000'

>>> OptimismBroadcaster.set_ovm_messenger(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1)

>>> OptimismBroadcaster.ovm_messenger()
'0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1'

Arbitrum

More on how L1 to L2 messaging on Arbitrum works can be found on the official Arbitrum documentation.

broadcast

ArbitrumBroadcaster.broadcast(_messages: DynArray[Message, MAX_MESSAGES], _gas_limit: uint256, _max_fee_per_gas: uint256)

Guarded Method

This function is only callable by one of the agents (ownership, parameter or emergency).

Function to broadcast a sequence of messages to the Relayer contract on a L2.

Input Type Description
_messages DynArray[Message, MAX_MESSAGES] Sequence of messages to broadcast
_gas_limit uint256 Gas limit for execution on L2
_max_fee_per_gas uint256 maximum gas price bid for the execution on L2
Source code
agent: HashMap[address, Agent]

arb_inbox: public(address)
arb_refund: public(address)

@external
def broadcast(_messages: DynArray[Message, MAX_MESSAGES], _gas_limit: uint256, _max_fee_per_gas: uint256):
    """
    @notice Broadcast a sequence of messeages.
    @param _messages The sequence of messages to broadcast.
    @param _gas_limit The gas limit for the execution on L2.
    @param _max_fee_per_gas The maximum gas price bid for the execution on L2.
    """
    agent: Agent = self.agent[msg.sender]
    assert agent != empty(Agent)

    # define all variables here before expanding memory enormously
    arb_inbox: address = self.arb_inbox
    arb_refund: address = self.arb_refund
    submission_cost: uint256 = 0

    data: Bytes[MAXSIZE] = _abi_encode(
        agent,
        _messages,
        method_id=method_id("relay(uint256,(address,bytes)[])"),
    )
    submission_cost = IArbInbox(arb_inbox).calculateRetryableSubmissionFee(len(data), block.basefee)

    # NOTE: using `unsafeCreateRetryableTicket` so that refund address is not aliased
    raw_call(
        arb_inbox,
        _abi_encode(
            self,  # to
            empty(uint256),  # l2CallValue
            submission_cost,  # maxSubmissionCost
            arb_refund,  # excessFeeRefundAddress
            arb_refund,  # callValueRefundAddress
            _gas_limit,
            _max_fee_per_gas,
            data,
            method_id=method_id("unsafeCreateRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)"),
        ),
        value=submission_cost + _gas_limit * _max_fee_per_gas,
    )
>>> soon

arb_inbox

Broadcaster.arb_inbox() -> address: view

Getter for the Arbitrum Delayed Inbox contract.

Source code
arb_inbox: public(address)
>>> Broadcaster.arb_inbox()
'0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f'

arb_refund

Broadcaster.arb_refund() -> address: view

Getter for the refund address, which is the L2 Vault.

Source code
arb_refund: public(address)
>>> Broadcaster.arb_refund()
'0x25877b9413Cc7832A6d142891b50bd53935feF82'

set_arb_inbox

Broadcaster.set_arb_inbox(_arb_inbox: address):

Guarded Method

This function is only callable by the ownership admin.

Function to set a new Arbitrum Inbox contract.

Emits: SetArbInbox

Input Type Description
_arb_inbox address New Arbitrum inbox address
Source code
event SetArbInbox:
    arb_inbox: address

@external
def set_arb_inbox(_arb_inbox: address):
    assert msg.sender == self.admins.ownership

    self.arb_inbox = _arb_inbox
    log SetArbInbox(_arb_inbox)
>>> soon

set_arb_refund

Broadcaster.set_arb_refund(_arb_refund: address):

Guarded Method

This function is only callable by the ownership admin.

Function to set a new refund address.

Emits: SetArbRefund

Input Type Description
set_arb_refund address New refund address
Source code
event SetArbRefund:
    arb_refund: address

@external
def set_arb_refund(_arb_refund: address):
    assert msg.sender == self.admins.ownership

    self.arb_refund = _arb_refund
    log SetArbRefund(_arb_refund)
>>> soon

Other Chains

Outside of Arbitrum, Optimism, and Optimistic Rollups, Curves cross-chain infrastructure uses a single XYZBroadcaster.vy contract deployed at 0x5786696bB5bE7fCDb9997E7f89355d9e97FF8d89.

This contract is responsible for broadcasting messages across several blockchains including Avalanche, Fantom, BinanceSmartChain, Kava, and Polygon.

broadcast

XYZBroadcaster.broadcast(_chain_id: uint256, _messages: DynArray[Message, MAX_MESSAGES])

Guarded Method

This function is only callable by one of the agents (ownership, parameter or emergency).

Function to broadcast a sequence of messages to the Relayer contract on a L2.

Input Type Description
_chain_id uint256 Chain ID to broadcast to
_messages DynArray[Message, MAX_MESSAGES] Sequence of messages to broadcast
Source code
event Broadcast:
    agent: Agent
    chain_id: uint256
    nonce: uint256
    digest: bytes32

enum Agent:
    OWNERSHIP
    PARAMETER
    EMERGENCY

admins: public(AdminSet)
future_admins: public(AdminSet)

agent: HashMap[address, Agent]

nonce: public(HashMap[Agent, HashMap[uint256, uint256]])  # agent -> chainId -> nonce
digest: public(HashMap[Agent, HashMap[uint256, HashMap[uint256, bytes32]]])  # agent -> chainId -> nonce -> messageDigest

@external
def broadcast(_chain_id: uint256, _messages: DynArray[Message, MAX_MESSAGES]):
    """
    @notice Broadcast a sequence of messeages.
    @param _chain_id The chain id to have messages executed on.
    @param _messages The sequence of messages to broadcast.
    """
    agent: Agent = self.agent[msg.sender]
    assert agent != empty(Agent)

    digest: bytes32 = keccak256(_abi_encode(_messages))
    nonce: uint256 = self.nonce[agent][_chain_id]

    self.digest[agent][_chain_id][nonce] = digest
    self.nonce[agent][_chain_id] = nonce + 1

    log Broadcast(agent, _chain_id, nonce, digest)
>>> XYZBroadcaster.broadcast
'todo'