Cross Domain Messaging
Cross Domain Messaging is a great approach to communicating between multiple domains and it allows us to send or receive messages to or from different domains. This section covers the sending and relaying of messages, either from L2 to L1 or from L1 to L2.
Note the following points in the case of Cross Domain Messaging:
- From L2 to L1: Messages are validated by verifying the inclusion of the message data in a mapping of a contract on the L2 state.
- From L1 to L2: Messages are validated simply by checking that the
ovmL1TXORIGINmatches the expected address.
We have 2 low-level bridge contracts known as the L1 and L2 Cross Domain Messengers. These contracts are paired in the sense that they reference each other’s addresses in order to validate cross domain messages.
- Stage 1: Any account on L2 may call the
L2CrossDomainMessenger.sendMessage()function with the information for the L1 message (
aka xDomainCalldata). (i.e. _target, msg.sender, _message)
- Stage 2: This data is hashed with the
messageNoncestorage variable and the hash is stored in the
- Stage 3: The
messageNonceis then incremented.
- The L2CrossDomainMessenger contract then passes the
OVM_L2ToL1MessagePasser.passMessageToL1()function. Note that
xDomainCalldatais hashed with
msg.sender(i.e. ovmCaller) and written to the
- Stage 1: The Relayer may call
L1CrossDomainMessenger.relayMessage(), providing the raw message inputs and an L2 inclusion proof.
- Stage 2: The validity of the message is confirmed by the following functions:
_verifyStateRootProof(): This function checks that the fraud-proof window has been closed for the batch in which our transaction belongs to that. The function also checks that the batch is stored in the
_verifyStorageProof(): This function checks the proof to confirm that the message data provided is in the
OVM_L2ToL1MessagePasser.sentMessagesmapping. The function also checks that our transaction has not already been written to the
- Stage 3: The address of the L2
ovmCALLERis then written to the
- The call is then executed, allowing the target to query the value of the
- Stage 4: In the case of a successful condition, it is added to the
successfulMessagesand cannot be relayed again.
- Stage 5: Regardless of the successful condition, an entry is written to the
- The receiver (i.e.
SynthetixBridgeToOptimism) checks that the caller is the
- Stage 1: Any account may call the
L1xDM’s sendMessage()function, specifying the details of the call that the L2xDM should make.
- Stage 2: The L1xDM calls
enqueueon the CTC to add to the transaction queue with the L2xDM as the
target. Note that the Transaction.data field should be ABI encoded to call the
- Stage 1: A transaction will be sent to the
- Stage 2: The cross domain message is deemed valid if the
ovmL1TXORIGINis in the
L1CrossDomainMessengercontract. If it is not valid, the execution reverts.
- Stage 3: If the message is valid, the arguments are ABI encoded and keccak256-hashed to
- Stage 4: The
successfulMessagesmapping is checked to verify that
xDomainCalldataHashhas not already been executed successfully. Note that if an entry is found in
successfulMessages, execution reverts.
- Stage 5: A check is done to disallow calls to the
OVM_L2ToL1MessagePasser, which would allow an attacker to spoof a withdrawal. In this stage, execution reverts if the check fails.
- Future note: The
OVM_L2ToL1MessagePasser and this check should be removed in favor of putting the
sentMessagesmapping into the L2xDM.
- Stage 6: The address of the L2
ovmCALLERis then written to the
xDomainMessageSenderstate variable. The call is then executed, allowing the target to query the value of the
- Stage 7: In the case of a successful condition, the message is added to the