1use alloc::{vec, vec::Vec};
7use alloy_primitives::{Bytes, ChainId, Log, keccak256};
8use alloy_sol_types::{SolEvent, sol};
9use derive_more::{AsRef, Constructor, From};
10use kona_protocol::Predeploys;
11use op_alloy_consensus::OpReceiptEnvelope;
12
13sol! {
14 #[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 #[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 function executeMessage(
42 MessageIdentifier calldata _id,
43 address _target,
44 bytes calldata _message
45 ) external;
46}
47
48#[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#[derive(Default, Debug, PartialEq, Eq, Clone, Constructor)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub struct ExecutingDescriptor {
80 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
82 pub timestamp: u64,
83 #[cfg_attr(
86 feature = "serde",
87 serde(
88 default,
89 skip_serializing_if = "Option::is_none",
90 with = "alloy_serde::quantity::opt"
91 )
92 )]
93 pub timeout: Option<u64>,
94 #[cfg_attr(
96 feature = "serde",
97 serde(
98 default,
99 rename = "chainID",
100 skip_serializing_if = "Option::is_none",
101 with = "alloy_serde::quantity::opt"
102 )
103 )]
104 pub chain_id: Option<ChainId>,
105}
106
107#[derive(Debug)]
110pub struct EnrichedExecutingMessage {
111 pub inner: ExecutingMessage,
113 pub executing_chain_id: u64,
115 pub executing_timestamp: u64,
117}
118
119impl EnrichedExecutingMessage {
120 pub const fn new(
122 inner: ExecutingMessage,
123 executing_chain_id: u64,
124 executing_timestamp: u64,
125 ) -> Self {
126 Self { inner, executing_chain_id, executing_timestamp }
127 }
128}
129
130pub fn extract_executing_messages(receipts: &[OpReceiptEnvelope]) -> Vec<ExecutingMessage> {
136 receipts.iter().fold(Vec::new(), |mut acc, envelope| {
137 let executing_messages = envelope.logs().iter().filter_map(parse_log_to_executing_message);
138
139 acc.extend(executing_messages);
140 acc
141 })
142}
143
144pub fn parse_logs_to_executing_msgs<'a>(
148 logs: impl Iterator<Item = &'a Log>,
149) -> impl Iterator<Item = Option<ExecutingMessage>> {
150 logs.map(parse_log_to_executing_message)
151}
152
153pub fn parse_log_to_executing_message(log: &Log) -> Option<ExecutingMessage> {
158 (log.address == Predeploys::CROSS_L2_INBOX && log.topics().len() == 2)
159 .then(|| ExecutingMessage::decode_log_data(&log.data).ok())
160 .flatten()
161}
162
163#[cfg(test)]
164mod tests {
165 use alloy_primitives::{Address, B256, LogData, U256};
166
167 use super::*;
168
169 #[cfg(feature = "serde")]
171 #[test]
172 fn test_serialize_executing_descriptor() {
173 let descriptor = ExecutingDescriptor {
174 timestamp: 1234567890,
175 timeout: Some(3600),
176 chain_id: Some(1000),
177 };
178 let serialized = serde_json::to_string(&descriptor).unwrap();
179 let expected = r#"{"timestamp":"0x499602d2","timeout":"0xe10","chainID":"0x3e8"}"#;
180 assert_eq!(serialized, expected);
181
182 let deserialized: ExecutingDescriptor = serde_json::from_str(&serialized).unwrap();
183 assert_eq!(descriptor, deserialized);
184 }
185
186 #[cfg(feature = "serde")]
187 #[test]
188 fn test_deserialize_executing_descriptor_missing_chain_id() {
189 let json = r#"{
190 "timestamp": "0x499602d2",
191 "timeout": "0xe10"
192 }"#;
193
194 let expected =
195 ExecutingDescriptor { timestamp: 1234567890, timeout: Some(3600), chain_id: None };
196
197 let deserialized: ExecutingDescriptor = serde_json::from_str(json).unwrap();
198 assert_eq!(deserialized, expected);
199 }
200
201 #[cfg(feature = "serde")]
202 #[test]
203 fn test_deserialize_executing_descriptor_missing_timeout() {
204 let json = r#"{
205 "timestamp": "0x499602d2",
206 "chainID": "0x3e8"
207 }"#;
208
209 let expected =
210 ExecutingDescriptor { timestamp: 1234567890, timeout: None, chain_id: Some(1000) };
211
212 let deserialized: ExecutingDescriptor = serde_json::from_str(json).unwrap();
213 assert_eq!(deserialized, expected);
214 }
215
216 #[test]
217 fn test_parse_logs_to_executing_msgs_iterator() {
218 let identifier = MessageIdentifier {
220 origin: Address::repeat_byte(0x77),
221 blockNumber: U256::from(200),
222 logIndex: U256::from(3),
223 timestamp: U256::from(777777),
224 chainId: U256::from(12),
225 };
226 let payload_hash = B256::repeat_byte(0x88);
227 let event = ExecutingMessage { payloadHash: payload_hash, identifier };
228 let data = ExecutingMessage::encode_log_data(&event);
229
230 let valid_log = Log { address: Predeploys::CROSS_L2_INBOX, data };
231 let invalid_log = Log {
232 address: Address::repeat_byte(0x99),
233 data: LogData::new_unchecked([B256::ZERO, B256::ZERO].to_vec(), Bytes::default()),
234 };
235
236 let logs = vec![&valid_log, &invalid_log];
237 let mut iter = parse_logs_to_executing_msgs(logs.into_iter());
238 assert_eq!(iter.next().unwrap().unwrap(), event);
239 assert!(iter.next().unwrap().is_none());
240 }
241}