kona_interop/
message.rs

1//! Interop message primitives.
2//!
3//! <https://specs.optimism.io/interop/messaging.html#messaging>
4//! <https://github.com/ethereum-optimism/optimism/blob/34d5f66ade24bd1f3ce4ce7c0a6cfc1a6540eca1/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol>
5
6use crate::constants::CROSS_L2_INBOX_ADDRESS;
7use alloc::{vec, vec::Vec};
8use alloy_primitives::{Bytes, Log, keccak256};
9use alloy_sol_types::{SolEvent, sol};
10use derive_more::{AsRef, From};
11use op_alloy_consensus::OpReceiptEnvelope;
12
13sol! {
14    /// @notice The struct for a pointer to a message payload in a remote (or local) chain.
15    #[derive(Default, Debug, PartialEq, Eq)]
16    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17    struct MessageIdentifier {
18        address origin;
19        uint256 blockNumber;
20        uint256 logIndex;
21        uint256 timestamp;
22        #[cfg_attr(feature = "serde", serde(rename = "chainID"))]
23        uint256 chainId;
24    }
25
26    /// @notice Emitted when a cross chain message is being executed.
27    /// @param payloadHash Hash of message payload being executed.
28    /// @param identifier Encoded Identifier of the message.
29    ///
30    /// Parameter names are derived from the `op-supervisor` JSON field names.
31    /// See the relevant definition in the Optimism repository:
32    /// [Ethereum-Optimism/op-supervisor](https://github.com/ethereum-optimism/optimism/blob/4ba2eb00eafc3d7de2c8ceb6fd83913a8c0a2c0d/op-supervisor/supervisor/types/types.go#L61-L64).
33    #[derive(Default, Debug, PartialEq, Eq)]
34    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35    event ExecutingMessage(bytes32 indexed payloadHash, MessageIdentifier identifier);
36
37    /// @notice Executes a cross chain message on the destination chain.
38    /// @param _id      Identifier of the message.
39    /// @param _target  Target address to call.
40    /// @param _message Message payload to call target with.
41    function executeMessage(
42        MessageIdentifier calldata _id,
43        address _target,
44        bytes calldata _message
45    ) external;
46}
47
48/// A [RawMessagePayload] is the raw payload of an initiating message.
49#[derive(Debug, Clone, From, AsRef, PartialEq, Eq)]
50pub struct RawMessagePayload(Bytes);
51
52impl From<&Log> for RawMessagePayload {
53    fn from(log: &Log) -> Self {
54        let mut data = vec![0u8; log.topics().len() * 32 + log.data.data.len()];
55        for (i, topic) in log.topics().iter().enumerate() {
56            data[i * 32..(i + 1) * 32].copy_from_slice(topic.as_ref());
57        }
58        data[(log.topics().len() * 32)..].copy_from_slice(log.data.data.as_ref());
59        data.into()
60    }
61}
62
63impl From<Vec<u8>> for RawMessagePayload {
64    fn from(data: Vec<u8>) -> Self {
65        Self(Bytes::from(data))
66    }
67}
68
69impl From<executeMessageCall> for ExecutingMessage {
70    fn from(call: executeMessageCall) -> Self {
71        Self { identifier: call._id, payloadHash: keccak256(call._message.as_ref()) }
72    }
73}
74
75/// A wrapper type for [ExecutingMessage] containing the chain ID of the chain that the message was
76/// executed on.
77#[derive(Debug)]
78pub struct EnrichedExecutingMessage {
79    /// The inner [ExecutingMessage].
80    pub inner: ExecutingMessage,
81    /// The chain ID of the chain that the message was executed on.
82    pub executing_chain_id: u64,
83    /// The timestamp of the block that the executing message was included in.
84    pub executing_timestamp: u64,
85}
86
87impl EnrichedExecutingMessage {
88    /// Create a new [EnrichedExecutingMessage] from an [ExecutingMessage] and a chain ID.
89    pub const fn new(
90        inner: ExecutingMessage,
91        executing_chain_id: u64,
92        executing_timestamp: u64,
93    ) -> Self {
94        Self { inner, executing_chain_id, executing_timestamp }
95    }
96}
97
98/// Extracts all [ExecutingMessage] events from list of [OpReceiptEnvelope]s.
99///
100/// See [`parse_log_to_executing_message`].
101///
102/// Note: filters out logs that don't contain executing message events.
103pub fn extract_executing_messages(receipts: &[OpReceiptEnvelope]) -> Vec<ExecutingMessage> {
104    receipts.iter().fold(Vec::new(), |mut acc, envelope| {
105        let executing_messages = envelope.logs().iter().filter_map(parse_log_to_executing_message);
106
107        acc.extend(executing_messages);
108        acc
109    })
110}
111
112/// Parses [`Log`]s to [`ExecutingMessage`]s.
113///
114/// See [`parse_log_to_executing_message`] for more details. Return iterator maps 1-1 with input.
115pub fn parse_logs_to_executing_msgs<'a>(
116    logs: impl Iterator<Item = &'a Log>,
117) -> impl Iterator<Item = Option<ExecutingMessage>> {
118    logs.map(parse_log_to_executing_message)
119}
120
121/// Parse [`Log`] to [`ExecutingMessage`], if any.
122///
123/// Max one [`ExecutingMessage`] event can exist per log. Returns `None` if log doesn't contain
124/// executing message event.
125pub fn parse_log_to_executing_message(log: &Log) -> Option<ExecutingMessage> {
126    (log.address == CROSS_L2_INBOX_ADDRESS && log.topics().len() == 2)
127        .then(|| ExecutingMessage::decode_log_data(&log.data, true).ok())
128        .flatten()
129}