use alloc::{vec, vec::Vec};
use alloy_primitives::{Bytes, ChainId, Log, keccak256};
use alloy_sol_types::{SolEvent, sol};
use derive_more::{AsRef, Constructor, From};
use kona_protocol::Predeploys;
use op_alloy_consensus::OpReceiptEnvelope;
sol! {
#[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct MessageIdentifier {
address origin;
uint256 blockNumber;
uint256 logIndex;
uint256 timestamp;
#[cfg_attr(feature = "serde", serde(rename = "chainID"))]
uint256 chainId;
}
#[derive(Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
event ExecutingMessage(bytes32 indexed payloadHash, MessageIdentifier identifier);
function executeMessage(
MessageIdentifier calldata _id,
address _target,
bytes calldata _message
) external;
}
#[derive(Debug, Clone, From, AsRef, PartialEq, Eq)]
pub struct RawMessagePayload(Bytes);
impl From<&Log> for RawMessagePayload {
fn from(log: &Log) -> Self {
let mut data = vec![0u8; log.topics().len() * 32 + log.data.data.len()];
for (i, topic) in log.topics().iter().enumerate() {
data[i * 32..(i + 1) * 32].copy_from_slice(topic.as_ref());
}
data[(log.topics().len() * 32)..].copy_from_slice(log.data.data.as_ref());
data.into()
}
}
impl From<Vec<u8>> for RawMessagePayload {
fn from(data: Vec<u8>) -> Self {
Self(Bytes::from(data))
}
}
impl From<executeMessageCall> for ExecutingMessage {
fn from(call: executeMessageCall) -> Self {
Self { identifier: call._id, payloadHash: keccak256(call._message.as_ref()) }
}
}
#[derive(Default, Debug, PartialEq, Eq, Clone, Constructor)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExecutingDescriptor {
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
pub timestamp: u64,
#[cfg_attr(
feature = "serde",
serde(
default,
skip_serializing_if = "Option::is_none",
with = "alloy_serde::quantity::opt"
)
)]
pub timeout: Option<u64>,
#[cfg_attr(
feature = "serde",
serde(
default,
rename = "chainID",
skip_serializing_if = "Option::is_none",
with = "alloy_serde::quantity::opt"
)
)]
pub chain_id: Option<ChainId>,
}
#[derive(Debug)]
pub struct EnrichedExecutingMessage {
pub inner: ExecutingMessage,
pub executing_chain_id: u64,
pub executing_timestamp: u64,
}
impl EnrichedExecutingMessage {
pub const fn new(
inner: ExecutingMessage,
executing_chain_id: u64,
executing_timestamp: u64,
) -> Self {
Self { inner, executing_chain_id, executing_timestamp }
}
}
pub fn extract_executing_messages(receipts: &[OpReceiptEnvelope]) -> Vec<ExecutingMessage> {
receipts.iter().fold(Vec::new(), |mut acc, envelope| {
let executing_messages = envelope.logs().iter().filter_map(parse_log_to_executing_message);
acc.extend(executing_messages);
acc
})
}
pub fn parse_logs_to_executing_msgs<'a>(
logs: impl Iterator<Item = &'a Log>,
) -> impl Iterator<Item = Option<ExecutingMessage>> {
logs.map(parse_log_to_executing_message)
}
pub fn parse_log_to_executing_message(log: &Log) -> Option<ExecutingMessage> {
(log.address == Predeploys::CROSS_L2_INBOX && log.topics().len() == 2)
.then(|| ExecutingMessage::decode_log_data(&log.data).ok())
.flatten()
}
#[cfg(test)]
mod tests {
use alloy_primitives::{Address, B256, LogData, U256};
use super::*;
#[cfg(feature = "serde")]
#[test]
fn test_serialize_executing_descriptor() {
let descriptor = ExecutingDescriptor {
timestamp: 1234567890,
timeout: Some(3600),
chain_id: Some(1000),
};
let serialized = serde_json::to_string(&descriptor).unwrap();
let expected = r#"{"timestamp":"0x499602d2","timeout":"0xe10","chainID":"0x3e8"}"#;
assert_eq!(serialized, expected);
let deserialized: ExecutingDescriptor = serde_json::from_str(&serialized).unwrap();
assert_eq!(descriptor, deserialized);
}
#[cfg(feature = "serde")]
#[test]
fn test_deserialize_executing_descriptor_missing_chain_id() {
let json = r#"{
"timestamp": "0x499602d2",
"timeout": "0xe10"
}"#;
let expected =
ExecutingDescriptor { timestamp: 1234567890, timeout: Some(3600), chain_id: None };
let deserialized: ExecutingDescriptor = serde_json::from_str(json).unwrap();
assert_eq!(deserialized, expected);
}
#[cfg(feature = "serde")]
#[test]
fn test_deserialize_executing_descriptor_missing_timeout() {
let json = r#"{
"timestamp": "0x499602d2",
"chainID": "0x3e8"
}"#;
let expected =
ExecutingDescriptor { timestamp: 1234567890, timeout: None, chain_id: Some(1000) };
let deserialized: ExecutingDescriptor = serde_json::from_str(json).unwrap();
assert_eq!(deserialized, expected);
}
#[test]
fn test_parse_logs_to_executing_msgs_iterator() {
let identifier = MessageIdentifier {
origin: Address::repeat_byte(0x77),
blockNumber: U256::from(200),
logIndex: U256::from(3),
timestamp: U256::from(777777),
chainId: U256::from(12),
};
let payload_hash = B256::repeat_byte(0x88);
let event = ExecutingMessage { payloadHash: payload_hash, identifier };
let data = ExecutingMessage::encode_log_data(&event);
let valid_log = Log { address: Predeploys::CROSS_L2_INBOX, data };
let invalid_log = Log {
address: Address::repeat_byte(0x99),
data: LogData::new_unchecked([B256::ZERO, B256::ZERO].to_vec(), Bytes::default()),
};
let logs = vec![&valid_log, &invalid_log];
let mut iter = parse_logs_to_executing_msgs(logs.into_iter());
assert_eq!(iter.next().unwrap().unwrap(), event);
assert!(iter.next().unwrap().is_none());
}
}