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:
-
ArbitrumBroadcaster.vy
for Arbitrum -
OptimismBroadcaster.vy
for Optimism and Optimistic Rollups -
GnosisBroadcaster.vy
for Gnosis -
XYZBroadcaster.vy
for all other chains
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:
- Ownership Admin:
0x40907540d8a6C65c637785e8f8B742ae6b0b9968
- Parameter Admin:
0x4EEb3bA4f221cA16ed4A0cC7254E2E32DF948c5f
- Emergency Admin:
0x467947EE34aF926cF1DCac093870f613C96B1E0c
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)"),
),
)
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
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.
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
.
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
.
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,
)
arb_inbox
¶
Broadcaster.arb_inbox() -> address: view
Getter for the Arbitrum Delayed Inbox contract.
arb_refund
¶
Broadcaster.arb_refund() -> address: view
Getter for the refund address, which is the L2 Vault.
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
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
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)