Skip to main content

ethrex_l2_common/
messages.rs

1use std::collections::BTreeMap;
2use std::sync::LazyLock;
3
4use bytes::Bytes;
5use ethereum_types::{Address, H256};
6use ethrex_common::types::balance_diff::{AssetDiff, BalanceDiff};
7use ethrex_common::utils::keccak;
8use ethrex_common::{H160, U256, types::Receipt};
9
10use serde::{Deserialize, Serialize};
11use tracing::warn;
12pub const MESSENGER_ADDRESS: Address = H160([
13    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14    0x00, 0x00, 0xff, 0xfe,
15]);
16
17pub static L1MESSAGE_EVENT_SELECTOR: LazyLock<H256> =
18    LazyLock::new(|| keccak("L1Message(address,bytes32,uint256)".as_bytes()));
19
20// keccak256("L2Message(uint256,address,address,uint256,uint256,uint256,bytes)")
21pub static L2MESSAGE_EVENT_SELECTOR: LazyLock<H256> = LazyLock::new(|| {
22    keccak("L2Message(uint256,address,address,uint256,uint256,uint256,bytes)".as_bytes())
23});
24
25// crosschainMintERC20(address,address,address,address,uint256)
26pub static CROSSCHAIN_MINT_ERC20_SELECTOR: [u8; 4] = [0xf0, 0x26, 0x31, 0x95];
27
28pub const BRIDGE_ADDRESS: Address = H160([
29    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
30    0x00, 0x00, 0xff, 0xff,
31]);
32
33#[derive(Serialize, Deserialize, Debug)]
34pub struct L1MessageProof {
35    pub batch_number: u64,
36    pub message_id: U256,
37    pub message_hash: H256,
38    pub merkle_proof: Vec<H256>,
39}
40
41#[derive(Debug, Clone, Default, Serialize, Deserialize)]
42/// Represents a message from the L2 to the L1
43pub struct L1Message {
44    /// Address that called the L1Messanger
45    pub from: Address,
46    /// Hash of the data given to the L1Messenger
47    pub data_hash: H256,
48    /// Message id emitted by the bridge contract
49    pub message_id: U256,
50}
51
52impl L1Message {
53    pub fn encode(&self) -> Vec<u8> {
54        let mut bytes = Vec::new();
55        bytes.extend_from_slice(&self.from.to_fixed_bytes());
56        bytes.extend_from_slice(&self.data_hash.0);
57        bytes.extend_from_slice(&self.message_id.to_big_endian());
58        bytes
59    }
60}
61
62pub fn get_l1_message_hash(msg: &L1Message) -> H256 {
63    keccak(msg.encode())
64}
65
66pub fn get_l2_message_hash(msg: &L2Message) -> H256 {
67    keccak(msg.encode())
68}
69
70pub fn get_block_l1_messages(receipts: &[Receipt]) -> Vec<L1Message> {
71    receipts
72        .iter()
73        .flat_map(|receipt| {
74            receipt
75                .logs
76                .iter()
77                .filter(|log| {
78                    log.address == MESSENGER_ADDRESS
79                        && log.topics.contains(&L1MESSAGE_EVENT_SELECTOR)
80                })
81                .flat_map(|log| -> Option<L1Message> {
82                    Some(L1Message {
83                        from: Address::from_slice(&log.topics.get(1)?.0[12..32]),
84                        data_hash: *log.topics.get(2)?,
85                        message_id: U256::from_big_endian(&log.topics.get(3)?.to_fixed_bytes()),
86                    })
87                })
88        })
89        .collect()
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93/// Represents a message from the L2 to another L2
94pub struct L2Message {
95    /// Chain id of the destination chain
96    pub dest_chain_id: U256,
97    /// Chain id of the source chain
98    pub source_chain_id: u64,
99    /// Address that originated the transaction
100    pub from: Address,
101    /// Address of the recipient in the destination chain
102    pub to: Address,
103    /// Amount of ETH to send to the recipient
104    pub value: U256,
105    /// Gas limit for the transaction execution in the destination chain
106    pub gas_limit: U256,
107    /// Unique transaction id for the message in the destination chain
108    pub tx_id: U256,
109    /// Calldata for the transaction in the destination chain
110    pub data: Bytes,
111}
112
113impl L2Message {
114    pub fn encode(&self) -> Vec<u8> {
115        [
116            U256::from(self.source_chain_id).to_big_endian().as_ref(),
117            self.from.as_bytes(),
118            self.to.as_bytes(),
119            &self.tx_id.to_big_endian(),
120            &self.value.to_big_endian(),
121            &self.gas_limit.to_big_endian(),
122            keccak(&self.data).as_bytes(),
123        ]
124        .concat()
125    }
126    pub fn from_log(log: &ethrex_common::types::Log, source_chain_id: u64) -> Option<L2Message> {
127        let dest_chain_id = U256::from_big_endian(&log.topics.get(1)?.0);
128        let from = H256::from_slice(log.data.get(0..32)?);
129        let from = Address::from_slice(&from.as_fixed_bytes()[12..]);
130        let to = H256::from_slice(log.data.get(32..64)?);
131        let to = Address::from_slice(&to.as_fixed_bytes()[12..]);
132        let value = U256::from_big_endian(log.data.get(64..96)?);
133        let gas_limit = U256::from_big_endian(log.data.get(96..128)?);
134        let tx_id = U256::from_big_endian(log.data.get(128..160)?);
135        // 160 to 192 is the offset for calldata
136        let calldata_len = U256::from_big_endian(log.data.get(192..224)?);
137        let calldata = log
138            .data
139            .get(224..224 + usize::try_from(calldata_len).ok()?)?;
140
141        Some(L2Message {
142            dest_chain_id,
143            source_chain_id,
144            from,
145            to,
146            value,
147            gas_limit,
148            tx_id,
149            data: Bytes::copy_from_slice(calldata),
150        })
151    }
152}
153
154pub fn get_block_l2_out_messages(receipts: &[Receipt], source_chain_id: u64) -> Vec<L2Message> {
155    receipts
156        .iter()
157        .flat_map(|receipt| {
158            receipt
159                .logs
160                .iter()
161                .filter(|log| {
162                    log.address == MESSENGER_ADDRESS
163                        && log.topics.first() == Some(&*L2MESSAGE_EVENT_SELECTOR)
164                        && log.topics.len() >= 2 // need chainId
165                })
166                .filter_map(|log| L2Message::from_log(log, source_chain_id))
167        })
168        .collect()
169}
170
171pub fn get_balance_diffs(messages: &[L2Message]) -> Vec<BalanceDiff> {
172    let mut balance_diffs: BTreeMap<U256, BalanceDiff> = BTreeMap::new();
173    for message in messages {
174        let mut offset = 4;
175        let (value, value_per_token_decoded) = if let Some(selector) = message.data.get(..4)
176            && *selector == CROSSCHAIN_MINT_ERC20_SELECTOR
177        {
178            let Some(token_l1) = message.data.get(offset + 12..offset + 32) else {
179                warn!("Failed to decode token_l1 from crosschainMintERC20 message");
180                continue;
181            };
182            offset += 32;
183            let Some(token_src_l2) = message.data.get(offset + 12..offset + 32) else {
184                warn!("Failed to decode token_src_l2 from crosschainMintERC20 message");
185                continue;
186            };
187            offset += 32;
188            let Some(token_dst_l2) = message.data.get(offset + 12..offset + 32) else {
189                warn!("Failed to decode token_dst_l2 from crosschainMintERC20 message");
190                continue;
191            };
192            offset += 32;
193            offset += 32; // skip "to" param
194            let Some(value_bytes) = message.data.get(offset..offset + 32) else {
195                warn!("Failed to decode value from crosschainMintERC20 message");
196                continue;
197            };
198            (
199                U256::zero(),
200                Some(AssetDiff {
201                    token_l1: Address::from_slice(token_l1),
202                    token_src_l2: Address::from_slice(token_src_l2),
203                    token_dst_l2: Address::from_slice(token_dst_l2),
204                    value: U256::from_big_endian(value_bytes),
205                }),
206            )
207        } else {
208            let mut value = message.value;
209            if message.to == BRIDGE_ADDRESS && message.from == BRIDGE_ADDRESS {
210                // This is the mint transaction, ignore the value
211                value = U256::zero();
212            }
213            (value, None)
214        };
215        let entry = balance_diffs
216            .entry(message.dest_chain_id)
217            .or_insert(BalanceDiff {
218                chain_id: message.dest_chain_id,
219                value: U256::zero(),
220                value_per_token: Vec::new(),
221                message_hashes: Vec::new(),
222            });
223        if let Some(value_per_token_decoded) = value_per_token_decoded {
224            if let Some(existing) = entry.value_per_token.iter_mut().find(|v| {
225                v.token_l1 == value_per_token_decoded.token_l1
226                    && v.token_src_l2 == value_per_token_decoded.token_src_l2
227                    && v.token_dst_l2 == value_per_token_decoded.token_dst_l2
228            }) {
229                existing.value += value_per_token_decoded.value;
230            } else {
231                entry.value_per_token.push(value_per_token_decoded);
232            }
233        }
234        entry.value += value;
235        entry.message_hashes.push(get_l2_message_hash(message));
236    }
237    balance_diffs.into_values().collect()
238}