1use alloy_primitives::{Address, Bytes, FixedBytes, U256};
12
13use super::DomainId;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct MessageHeader {
35 pub version: u32,
37 pub source_domain: DomainId,
39 pub destination_domain: DomainId,
41 pub nonce: FixedBytes<32>,
43 pub sender: FixedBytes<32>,
45 pub recipient: FixedBytes<32>,
47 pub destination_caller: FixedBytes<32>,
49 pub min_finality_threshold: u32,
51 pub finality_threshold_executed: u32,
53}
54
55impl MessageHeader {
56 pub const SIZE: usize = 148;
58
59 #[allow(clippy::too_many_arguments)]
61 pub fn new(
62 version: u32,
63 source_domain: DomainId,
64 destination_domain: DomainId,
65 nonce: FixedBytes<32>,
66 sender: FixedBytes<32>,
67 recipient: FixedBytes<32>,
68 destination_caller: FixedBytes<32>,
69 min_finality_threshold: u32,
70 finality_threshold_executed: u32,
71 ) -> Self {
72 Self {
73 version,
74 source_domain,
75 destination_domain,
76 nonce,
77 sender,
78 recipient,
79 destination_caller,
80 min_finality_threshold,
81 finality_threshold_executed,
82 }
83 }
84
85 pub fn encode(&self) -> Bytes {
89 let mut bytes = Vec::with_capacity(Self::SIZE);
90
91 bytes.extend_from_slice(&self.version.to_be_bytes());
93 bytes.extend_from_slice(&self.source_domain.as_u32().to_be_bytes());
95 bytes.extend_from_slice(&self.destination_domain.as_u32().to_be_bytes());
97 bytes.extend_from_slice(self.nonce.as_slice());
99 bytes.extend_from_slice(self.sender.as_slice());
101 bytes.extend_from_slice(self.recipient.as_slice());
103 bytes.extend_from_slice(self.destination_caller.as_slice());
105 bytes.extend_from_slice(&self.min_finality_threshold.to_be_bytes());
107 bytes.extend_from_slice(&self.finality_threshold_executed.to_be_bytes());
109
110 Bytes::from(bytes)
111 }
112
113 pub fn decode(bytes: &[u8]) -> Option<Self> {
118 if bytes.len() < Self::SIZE {
119 return None;
120 }
121
122 let version = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
123
124 let source_domain = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
125 let source_domain = DomainId::from_u32(source_domain)?;
126
127 let destination_domain = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
128 let destination_domain = DomainId::from_u32(destination_domain)?;
129
130 let nonce = FixedBytes::from_slice(&bytes[12..44]);
131 let sender = FixedBytes::from_slice(&bytes[44..76]);
132 let recipient = FixedBytes::from_slice(&bytes[76..108]);
133 let destination_caller = FixedBytes::from_slice(&bytes[108..140]);
134
135 let min_finality_threshold =
136 u32::from_be_bytes([bytes[140], bytes[141], bytes[142], bytes[143]]);
137 let finality_threshold_executed =
138 u32::from_be_bytes([bytes[144], bytes[145], bytes[146], bytes[147]]);
139
140 Some(Self {
141 version,
142 source_domain,
143 destination_domain,
144 nonce,
145 sender,
146 recipient,
147 destination_caller,
148 min_finality_threshold,
149 finality_threshold_executed,
150 })
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct BurnMessageV2 {
174 pub version: u32,
176 pub burn_token: Address,
178 pub mint_recipient: Address,
180 pub amount: U256,
182 pub message_sender: Address,
184 pub max_fee: U256,
186 pub fee_executed: U256,
188 pub expiration_block: U256,
190 pub hook_data: Bytes,
192}
193
194impl BurnMessageV2 {
195 pub const MIN_SIZE: usize = 228;
197
198 pub fn new(
200 burn_token: Address,
201 mint_recipient: Address,
202 amount: U256,
203 message_sender: Address,
204 ) -> Self {
205 Self {
206 version: 1,
207 burn_token,
208 mint_recipient,
209 amount,
210 message_sender,
211 max_fee: U256::ZERO,
212 fee_executed: U256::ZERO,
213 expiration_block: U256::ZERO,
214 hook_data: Bytes::new(),
215 }
216 }
217
218 pub fn new_with_fast_transfer(
220 burn_token: Address,
221 mint_recipient: Address,
222 amount: U256,
223 message_sender: Address,
224 max_fee: U256,
225 ) -> Self {
226 Self {
227 version: 1,
228 burn_token,
229 mint_recipient,
230 amount,
231 message_sender,
232 max_fee,
233 fee_executed: U256::ZERO,
234 expiration_block: U256::ZERO,
235 hook_data: Bytes::new(),
236 }
237 }
238
239 pub fn new_with_hooks(
241 burn_token: Address,
242 mint_recipient: Address,
243 amount: U256,
244 message_sender: Address,
245 hook_data: Bytes,
246 ) -> Self {
247 Self {
248 version: 1,
249 burn_token,
250 mint_recipient,
251 amount,
252 message_sender,
253 max_fee: U256::ZERO,
254 fee_executed: U256::ZERO,
255 expiration_block: U256::ZERO,
256 hook_data,
257 }
258 }
259
260 pub fn with_hook_data(mut self, hook_data: Bytes) -> Self {
262 self.hook_data = hook_data;
263 self
264 }
265
266 pub fn with_max_fee(mut self, max_fee: U256) -> Self {
268 self.max_fee = max_fee;
269 self
270 }
271
272 pub fn with_expiration_block(mut self, expiration_block: U256) -> Self {
274 self.expiration_block = expiration_block;
275 self
276 }
277
278 pub fn has_hooks(&self) -> bool {
280 !self.hook_data.is_empty()
281 }
282
283 pub fn is_fast_transfer(&self) -> bool {
285 self.max_fee > U256::ZERO
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use alloy_primitives::address;
293
294 #[test]
295 fn test_message_header_size() {
296 assert_eq!(MessageHeader::SIZE, 148);
297 }
298
299 #[test]
300 fn test_message_header_encode_decode() {
301 let header = MessageHeader::new(
302 1,
303 DomainId::Ethereum,
304 DomainId::Arbitrum,
305 FixedBytes::from([1u8; 32]),
306 FixedBytes::from([2u8; 32]),
307 FixedBytes::from([3u8; 32]),
308 FixedBytes::from([0u8; 32]),
309 1000,
310 1000,
311 );
312
313 let encoded = header.encode();
314 assert_eq!(encoded.len(), MessageHeader::SIZE);
315
316 let decoded = MessageHeader::decode(&encoded).expect("should decode");
317 assert_eq!(header, decoded);
318 }
319
320 #[test]
321 fn test_message_header_decode_too_short() {
322 let short_bytes = vec![0u8; 100];
323 assert!(MessageHeader::decode(&short_bytes).is_none());
324 }
325
326 #[test]
327 fn test_message_header_decode_invalid_domain() {
328 let mut bytes = vec![0u8; MessageHeader::SIZE];
329 bytes[4..8].copy_from_slice(&999u32.to_be_bytes());
331 assert!(MessageHeader::decode(&bytes).is_none());
332 }
333
334 #[test]
335 fn test_burn_message_v2_new() {
336 let burn_token = address!("A2d2a41577ce14e20a6c2de999A8Ec2BD9fe34aF");
337 let mint_recipient = address!("742d35Cc6634C0532925a3b844Bc9e7595f8fA0d");
338 let amount = U256::from(1000000u64);
339 let sender = address!("1234567890abcdef1234567890abcdef12345678");
340
341 let msg = BurnMessageV2::new(burn_token, mint_recipient, amount, sender);
342
343 assert_eq!(msg.version, 1);
344 assert_eq!(msg.burn_token, burn_token);
345 assert_eq!(msg.mint_recipient, mint_recipient);
346 assert_eq!(msg.amount, amount);
347 assert_eq!(msg.message_sender, sender);
348 assert_eq!(msg.max_fee, U256::ZERO);
349 assert_eq!(msg.fee_executed, U256::ZERO);
350 assert_eq!(msg.expiration_block, U256::ZERO);
351 assert!(msg.hook_data.is_empty());
352 assert!(!msg.has_hooks());
353 assert!(!msg.is_fast_transfer());
354 }
355
356 #[test]
357 fn test_burn_message_v2_fast_transfer() {
358 let burn_token = address!("A2d2a41577ce14e20a6c2de999A8Ec2BD9fe34aF");
359 let mint_recipient = address!("742d35Cc6634C0532925a3b844Bc9e7595f8fA0d");
360 let amount = U256::from(1000000u64);
361 let sender = address!("1234567890abcdef1234567890abcdef12345678");
362 let max_fee = U256::from(100u64);
363
364 let msg = BurnMessageV2::new_with_fast_transfer(
365 burn_token,
366 mint_recipient,
367 amount,
368 sender,
369 max_fee,
370 );
371
372 assert_eq!(msg.max_fee, max_fee);
373 assert!(msg.is_fast_transfer());
374 assert!(!msg.has_hooks());
375 }
376
377 #[test]
378 fn test_burn_message_v2_with_hooks() {
379 let burn_token = address!("A2d2a41577ce14e20a6c2de999A8Ec2BD9fe34aF");
380 let mint_recipient = address!("742d35Cc6634C0532925a3b844Bc9e7595f8fA0d");
381 let amount = U256::from(1000000u64);
382 let sender = address!("1234567890abcdef1234567890abcdef12345678");
383 let hook_data = Bytes::from(vec![1, 2, 3, 4]);
384
385 let msg = BurnMessageV2::new_with_hooks(
386 burn_token,
387 mint_recipient,
388 amount,
389 sender,
390 hook_data.clone(),
391 );
392
393 assert_eq!(msg.hook_data, hook_data);
394 assert!(msg.has_hooks());
395 assert!(!msg.is_fast_transfer());
396 }
397
398 #[test]
399 fn test_burn_message_v2_builder() {
400 let burn_token = address!("A2d2a41577ce14e20a6c2de999A8Ec2BD9fe34aF");
401 let mint_recipient = address!("742d35Cc6634C0532925a3b844Bc9e7595f8fA0d");
402 let amount = U256::from(1000000u64);
403 let sender = address!("1234567890abcdef1234567890abcdef12345678");
404
405 let msg = BurnMessageV2::new(burn_token, mint_recipient, amount, sender)
406 .with_max_fee(U256::from(100u64))
407 .with_hook_data(Bytes::from(vec![1, 2, 3]))
408 .with_expiration_block(U256::from(1000u64));
409
410 assert!(msg.is_fast_transfer());
411 assert!(msg.has_hooks());
412 assert_eq!(msg.expiration_block, U256::from(1000u64));
413 }
414}