use alloc::{vec, vec::Vec};
use alloy_consensus::TxReceipt;
use alloy_primitives::{keccak256, Bytes, Log};
use alloy_sol_types::{sol, SolEvent};
use derive_more::{AsRef, From};
use crate::CROSS_L2_INBOX_ADDRESS;
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 msgHash, MessageIdentifier id);
function executeMessage(
MessageIdentifier calldata _id,
address _target,
bytes calldata _message
) external;
}
#[derive(Debug, Clone, From, AsRef, PartialEq, Eq)]
pub struct MessagePayload(Bytes);
impl From<&Log> for MessagePayload {
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 MessagePayload {
fn from(data: Vec<u8>) -> Self {
Self(Bytes::from(data))
}
}
impl From<executeMessageCall> for ExecutingMessage {
fn from(call: executeMessageCall) -> Self {
Self { id: call._id, msgHash: keccak256(call._message.as_ref()) }
}
}
#[derive(Debug)]
pub struct EnrichedExecutingMessage {
pub inner: ExecutingMessage,
pub executing_chain_id: u64,
}
impl EnrichedExecutingMessage {
pub const fn new(inner: ExecutingMessage, executing_chain_id: u64) -> Self {
Self { inner, executing_chain_id }
}
}
pub fn extract_executing_messages(receipts: &[impl TxReceipt<Log = Log>]) -> Vec<ExecutingMessage> {
receipts.iter().fold(Vec::new(), |mut acc, envelope| {
let executing_messages = envelope.logs().iter().filter_map(|log| {
(log.address == CROSS_L2_INBOX_ADDRESS && log.topics().len() == 2)
.then(|| ExecutingMessage::decode_log_data(&log.data, true).ok())
.flatten()
});
acc.extend(executing_messages);
acc
})
}
#[cfg(test)]
#[cfg(feature = "serde")]
mod tests {
use super::*;
use alloc::vec;
use alloy_consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom};
use alloy_primitives::{address, hex, keccak256, uint, LogData, B256, U256};
use alloy_sol_types::SolValue;
const MESSAGE: [u8; 4] = hex!("deadbeef");
#[test]
fn test_extract_executing_message_single() {
let message_hash: B256 = keccak256(MESSAGE);
let origin_address = address!("6887246668a3b87F54DeB3b94Ba47a6f63F32985");
let origin_log_index = 100;
let origin_timestamp = 100;
let origin_chain_id = 100;
let log = Log {
address: CROSS_L2_INBOX_ADDRESS,
data: LogData::new(
vec![ExecutingMessage::SIGNATURE_HASH, message_hash],
MessageIdentifier {
origin: origin_address,
blockNumber: U256::ZERO,
logIndex: U256::from(origin_log_index),
timestamp: U256::from(origin_timestamp),
chainId: U256::from(origin_chain_id),
}
.abi_encode()
.into(),
)
.unwrap(),
};
assert_eq!(log.topics().len(), 2);
let receipts = ReceiptEnvelope::Eip1559(ReceiptWithBloom {
receipt: Receipt { logs: vec![log], ..Default::default() },
..Default::default()
});
let messages = extract_executing_messages(&[receipts]);
assert_eq!(messages.len(), 1);
}
#[test]
fn test_message_identifier_serde() {
let raw_id = r#"
{
"origin": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985",
"blockNumber": 123456,
"logIndex": 789,
"timestamp": 1618932000,
"chainID": 420
}
"#;
let id: MessageIdentifier = serde_json::from_str(raw_id).unwrap();
let expected = MessageIdentifier {
origin: "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985".parse().unwrap(),
blockNumber: uint!(123456_U256),
logIndex: uint!(789_U256),
timestamp: uint!(1618932000_U256),
chainId: uint!(420_U256),
};
assert_eq!(id, expected);
}
#[test]
fn test_executing_message_serde() {
let raw_msg = r#"
{
"id": {
"origin": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985",
"blockNumber": 123456,
"logIndex": 789,
"timestamp": 1618932000,
"chainID": 420
},
"msgHash": "0xef8cc21bdbab8d2b60b054460768b1db67c8906b6a2bdf9bc287b3654326fc76"
}
"#;
let msg: ExecutingMessage = serde_json::from_str(raw_msg).unwrap();
let expected = ExecutingMessage {
id: MessageIdentifier {
origin: "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985".parse().unwrap(),
blockNumber: uint!(123456_U256),
logIndex: uint!(789_U256),
timestamp: uint!(1618932000_U256),
chainId: uint!(420_U256),
},
msgHash: "0xef8cc21bdbab8d2b60b054460768b1db67c8906b6a2bdf9bc287b3654326fc76"
.parse()
.unwrap(),
};
assert_eq!(msg, expected);
}
}