1use crate::{
73 common::decode::DecodeCompressedRlp,
74 e2s::{error::E2sError, types::Entry},
75};
76use alloy_consensus::{Block, BlockBody, Header};
77use alloy_primitives::{B256, U256};
78use alloy_rlp::{Decodable, Encodable};
79use sha2::{Digest, Sha256};
80use snap::{read::FrameDecoder, write::FrameEncoder};
81use std::{
82 io::{Read, Write},
83 marker::PhantomData,
84};
85
86pub const COMPRESSED_HEADER: [u8; 2] = [0x03, 0x00];
89
90pub const COMPRESSED_BODY: [u8; 2] = [0x04, 0x00];
92
93pub const COMPRESSED_RECEIPTS: [u8; 2] = [0x05, 0x00];
95
96pub const TOTAL_DIFFICULTY: [u8; 2] = [0x06, 0x00];
98
99pub const ACCUMULATOR: [u8; 2] = [0x07, 0x00];
101
102pub const MAX_BLOCKS_PER_ERA1: usize = 8192;
104
105#[derive(Debug, Clone, Default)]
107pub struct SnappyRlpCodec<T> {
108 _phantom: PhantomData<T>,
109}
110
111impl<T> SnappyRlpCodec<T> {
112 pub const fn new() -> Self {
114 Self { _phantom: PhantomData }
115 }
116}
117
118impl<T: Decodable> SnappyRlpCodec<T> {
119 pub fn decode(&self, compressed_data: &[u8]) -> Result<T, E2sError> {
121 let mut decoder = FrameDecoder::new(compressed_data);
122 let mut decompressed = Vec::new();
123 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
124 E2sError::SnappyDecompression(format!("Failed to decompress data: {e}"))
125 })?;
126
127 let mut slice = decompressed.as_slice();
128 T::decode(&mut slice).map_err(|e| E2sError::Rlp(format!("Failed to decode RLP data: {e}")))
129 }
130}
131
132impl<T: Encodable> SnappyRlpCodec<T> {
133 pub fn encode(&self, data: &T) -> Result<Vec<u8>, E2sError> {
135 let mut rlp_data = Vec::new();
136 data.encode(&mut rlp_data);
137
138 let mut compressed = Vec::new();
139 {
140 let mut encoder = FrameEncoder::new(&mut compressed);
141
142 Write::write_all(&mut encoder, &rlp_data).map_err(|e| {
143 E2sError::SnappyCompression(format!("Failed to compress data: {e}"))
144 })?;
145
146 encoder.flush().map_err(|e| {
147 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
148 })?;
149 }
150
151 Ok(compressed)
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct CompressedHeader {
158 pub data: Vec<u8>,
160}
161
162impl CompressedHeader {
163 pub const fn new(data: Vec<u8>) -> Self {
165 Self { data }
166 }
167
168 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
170 let mut compressed = Vec::new();
171 {
172 let mut encoder = FrameEncoder::new(&mut compressed);
173
174 Write::write_all(&mut encoder, rlp_data).map_err(|e| {
175 E2sError::SnappyCompression(format!("Failed to compress header: {e}"))
176 })?;
177
178 encoder.flush().map_err(|e| {
179 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
180 })?;
181 }
182 Ok(Self { data: compressed })
183 }
184
185 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
187 let mut decoder = FrameDecoder::new(self.data.as_slice());
188 let mut decompressed = Vec::new();
189 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
190 E2sError::SnappyDecompression(format!("Failed to decompress header: {e}"))
191 })?;
192
193 Ok(decompressed)
194 }
195
196 pub fn to_entry(&self) -> Entry {
198 Entry::new(COMPRESSED_HEADER, self.data.clone())
199 }
200
201 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
203 if entry.entry_type != COMPRESSED_HEADER {
204 return Err(E2sError::Ssz(format!(
205 "Invalid entry type for CompressedHeader: expected {:02x}{:02x}, got {:02x}{:02x}",
206 COMPRESSED_HEADER[0],
207 COMPRESSED_HEADER[1],
208 entry.entry_type[0],
209 entry.entry_type[1]
210 )));
211 }
212
213 Ok(Self { data: entry.data.clone() })
214 }
215
216 pub fn decode_header(&self) -> Result<Header, E2sError> {
218 self.decode()
219 }
220
221 pub fn from_header<H: Encodable>(header: &H) -> Result<Self, E2sError> {
223 let encoder = SnappyRlpCodec::new();
224 let compressed = encoder.encode(header)?;
225 Ok(Self::new(compressed))
226 }
227}
228
229impl DecodeCompressedRlp for CompressedHeader {
230 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
231 let decoder = SnappyRlpCodec::<T>::new();
232 decoder.decode(&self.data)
233 }
234}
235
236#[derive(Debug, Clone)]
238pub struct CompressedBody {
239 pub data: Vec<u8>,
241}
242
243impl CompressedBody {
244 pub const fn new(data: Vec<u8>) -> Self {
246 Self { data }
247 }
248
249 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
251 let mut compressed = Vec::new();
252 {
253 let mut encoder = FrameEncoder::new(&mut compressed);
254
255 Write::write_all(&mut encoder, rlp_data).map_err(|e| {
256 E2sError::SnappyCompression(format!("Failed to compress body: {e}"))
257 })?;
258
259 encoder.flush().map_err(|e| {
260 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
261 })?;
262 }
263 Ok(Self { data: compressed })
264 }
265
266 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
268 let mut decoder = FrameDecoder::new(self.data.as_slice());
269 let mut decompressed = Vec::new();
270 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
271 E2sError::SnappyDecompression(format!("Failed to decompress body: {e}"))
272 })?;
273
274 Ok(decompressed)
275 }
276
277 pub fn to_entry(&self) -> Entry {
279 Entry::new(COMPRESSED_BODY, self.data.clone())
280 }
281
282 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
284 if entry.entry_type != COMPRESSED_BODY {
285 return Err(E2sError::Ssz(format!(
286 "Invalid entry type for CompressedBody: expected {:02x}{:02x}, got {:02x}{:02x}",
287 COMPRESSED_BODY[0], COMPRESSED_BODY[1], entry.entry_type[0], entry.entry_type[1]
288 )));
289 }
290
291 Ok(Self { data: entry.data.clone() })
292 }
293
294 pub fn decode_body<T: Decodable, H: Decodable>(&self) -> Result<BlockBody<T, H>, E2sError> {
296 let decompressed = self.decompress()?;
297 Self::decode_body_from_decompressed(&decompressed)
298 }
299
300 pub fn decode_body_from_decompressed<T: Decodable, H: Decodable>(
302 data: &[u8],
303 ) -> Result<BlockBody<T, H>, E2sError> {
304 alloy_rlp::decode_exact::<BlockBody<T, H>>(data)
305 .map_err(|e| E2sError::Rlp(format!("Failed to decode RLP data: {e}")))
306 }
307
308 pub fn from_body<B: Encodable>(body: &B) -> Result<Self, E2sError> {
310 let encoder = SnappyRlpCodec::new();
311 let compressed = encoder.encode(body)?;
312 Ok(Self::new(compressed))
313 }
314}
315
316impl DecodeCompressedRlp for CompressedBody {
317 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
318 let decoder = SnappyRlpCodec::<T>::new();
319 decoder.decode(&self.data)
320 }
321}
322
323#[derive(Debug, Clone)]
325pub struct CompressedReceipts {
326 pub data: Vec<u8>,
328}
329
330impl CompressedReceipts {
331 pub const fn new(data: Vec<u8>) -> Self {
333 Self { data }
334 }
335
336 pub fn from_rlp(rlp_data: &[u8]) -> Result<Self, E2sError> {
338 let mut compressed = Vec::new();
339 {
340 let mut encoder = FrameEncoder::new(&mut compressed);
341
342 Write::write_all(&mut encoder, rlp_data).map_err(|e| {
343 E2sError::SnappyCompression(format!("Failed to compress receipts: {e}"))
344 })?;
345
346 encoder.flush().map_err(|e| {
347 E2sError::SnappyCompression(format!("Failed to flush encoder: {e}"))
348 })?;
349 }
350 Ok(Self { data: compressed })
351 }
352 pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
354 let mut decoder = FrameDecoder::new(self.data.as_slice());
355 let mut decompressed = Vec::new();
356 Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
357 E2sError::SnappyDecompression(format!("Failed to decompress receipts: {e}"))
358 })?;
359
360 Ok(decompressed)
361 }
362
363 pub fn to_entry(&self) -> Entry {
365 Entry::new(COMPRESSED_RECEIPTS, self.data.clone())
366 }
367
368 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
370 if entry.entry_type != COMPRESSED_RECEIPTS {
371 return Err(E2sError::Ssz(format!(
372 "Invalid entry type for CompressedReceipts: expected {:02x}{:02x}, got {:02x}{:02x}",
373 COMPRESSED_RECEIPTS[0], COMPRESSED_RECEIPTS[1],
374 entry.entry_type[0], entry.entry_type[1]
375 )));
376 }
377
378 Ok(Self { data: entry.data.clone() })
379 }
380
381 pub fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
383 let decoder = SnappyRlpCodec::<T>::new();
384 decoder.decode(&self.data)
385 }
386
387 pub fn from_encodable<T: Encodable>(data: &T) -> Result<Self, E2sError> {
389 let encoder = SnappyRlpCodec::<T>::new();
390 let compressed = encoder.encode(data)?;
391 Ok(Self::new(compressed))
392 }
393 pub fn encode_receipts_to_rlp<T: Encodable>(receipts: &[T]) -> Result<Vec<u8>, E2sError> {
395 let mut rlp_data = Vec::new();
396 alloy_rlp::encode_list(receipts, &mut rlp_data);
397 Ok(rlp_data)
398 }
399
400 pub fn from_encodable_list<T: Encodable>(receipts: &[T]) -> Result<Self, E2sError> {
402 let rlp_data = Self::encode_receipts_to_rlp(receipts)?;
403 Self::from_rlp(&rlp_data)
404 }
405}
406
407impl DecodeCompressedRlp for CompressedReceipts {
408 fn decode<T: Decodable>(&self) -> Result<T, E2sError> {
409 let decoder = SnappyRlpCodec::<T>::new();
410 decoder.decode(&self.data)
411 }
412}
413
414#[derive(Debug, Clone)]
416pub struct TotalDifficulty {
417 pub value: U256,
419}
420
421impl TotalDifficulty {
422 pub const fn new(value: U256) -> Self {
424 Self { value }
425 }
426
427 pub fn to_entry(&self) -> Entry {
429 let data = self.value.to_le_bytes::<32>().to_vec();
431 Entry::new(TOTAL_DIFFICULTY, data)
432 }
433
434 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
436 if entry.entry_type != TOTAL_DIFFICULTY {
437 return Err(E2sError::Ssz(format!(
438 "Invalid entry type for TotalDifficulty: expected {:02x}{:02x}, got {:02x}{:02x}",
439 TOTAL_DIFFICULTY[0], TOTAL_DIFFICULTY[1], entry.entry_type[0], entry.entry_type[1]
440 )));
441 }
442
443 if entry.data.len() != 32 {
444 return Err(E2sError::Ssz(format!(
445 "Invalid data length for TotalDifficulty: expected 32, got {}",
446 entry.data.len()
447 )));
448 }
449
450 let value = U256::from_le_slice(&entry.data);
452
453 Ok(Self { value })
454 }
455}
456
457#[derive(Debug, Clone)]
460pub struct Accumulator {
461 pub root: B256,
463}
464
465impl Accumulator {
466 pub const fn new(root: B256) -> Self {
468 Self { root }
469 }
470
471 pub fn to_entry(&self) -> Entry {
473 Entry::new(ACCUMULATOR, self.root.to_vec())
474 }
475
476 pub fn from_entry(entry: &Entry) -> Result<Self, E2sError> {
478 if entry.entry_type != ACCUMULATOR {
479 return Err(E2sError::Ssz(format!(
480 "Invalid entry type for Accumulator: expected {:02x}{:02x}, got {:02x}{:02x}",
481 ACCUMULATOR[0], ACCUMULATOR[1], entry.entry_type[0], entry.entry_type[1]
482 )));
483 }
484
485 if entry.data.len() != 32 {
486 return Err(E2sError::Ssz(format!(
487 "Invalid data length for Accumulator: expected 32, got {}",
488 entry.data.len()
489 )));
490 }
491
492 let mut root = [0u8; 32];
493 root.copy_from_slice(&entry.data);
494
495 Ok(Self { root: B256::from(root) })
496 }
497
498 pub fn from_header_records(records: &[HeaderRecord]) -> Result<Self, E2sError> {
508 let capacity = MAX_BLOCKS_PER_ERA1;
509
510 if records.len() > capacity {
511 return Err(E2sError::Ssz(format!(
512 "Too many header records: got {}, max {}",
513 records.len(),
514 capacity
515 )));
516 }
517
518 let mut leaves = Vec::with_capacity(capacity);
520 for record in records {
521 let mut data = [0u8; 64];
522 data[..32].copy_from_slice(record.block_hash.as_slice());
523 data[32..].copy_from_slice(&record.total_difficulty.to_le_bytes::<32>());
524 leaves.push(<[u8; 32]>::from(Sha256::digest(data)));
525 }
526
527 leaves.resize(capacity, [0u8; 32]);
529
530 while leaves.len() > 1 {
532 let mut next_level = Vec::with_capacity(leaves.len() / 2);
533 for pair in leaves.chunks_exact(2) {
534 let mut data = [0u8; 64];
535 data[..32].copy_from_slice(&pair[0]);
536 data[32..].copy_from_slice(&pair[1]);
537 next_level.push(<[u8; 32]>::from(Sha256::digest(data)));
538 }
539 leaves = next_level;
540 }
541
542 let merkle_root = leaves[0];
543
544 let mut mix = [0u8; 64];
546 mix[..32].copy_from_slice(&merkle_root);
547 let length = records.len() as u64;
548 mix[32..40].copy_from_slice(&length.to_le_bytes());
549 Ok(Self { root: B256::from(<[u8; 32]>::from(Sha256::digest(mix))) })
552 }
553}
554
555#[derive(Debug, Clone)]
559pub struct HeaderRecord {
560 pub block_hash: B256,
562 pub total_difficulty: U256,
564}
565
566#[derive(Debug, Clone)]
568pub struct BlockTuple {
569 pub header: CompressedHeader,
571
572 pub body: CompressedBody,
574
575 pub receipts: CompressedReceipts,
577
578 pub total_difficulty: TotalDifficulty,
580}
581
582impl BlockTuple {
583 pub const fn new(
585 header: CompressedHeader,
586 body: CompressedBody,
587 receipts: CompressedReceipts,
588 total_difficulty: TotalDifficulty,
589 ) -> Self {
590 Self { header, body, receipts, total_difficulty }
591 }
592
593 pub fn to_alloy_block<T: Decodable>(&self) -> Result<Block<T>, E2sError> {
595 let header: Header = self.header.decode()?;
596 let body: BlockBody<T> = self.body.decode()?;
597
598 Ok(Block::new(header, body))
599 }
600
601 pub fn from_alloy_block<T: Encodable, R: Encodable>(
603 block: &Block<T>,
604 receipts: &R,
605 total_difficulty: U256,
606 ) -> Result<Self, E2sError> {
607 let header = CompressedHeader::from_header(&block.header)?;
608 let body = CompressedBody::from_body(&block.body)?;
609
610 let compressed_receipts = CompressedReceipts::from_encodable(receipts)?;
611
612 let difficulty = TotalDifficulty::new(total_difficulty);
613
614 Ok(Self::new(header, body, compressed_receipts, difficulty))
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621 use crate::test_utils::{create_header, create_test_receipt, create_test_receipts};
622 use alloy_eips::eip4895::Withdrawals;
623 use alloy_primitives::{Bytes, U256};
624 use reth_ethereum_primitives::{Receipt, TxType};
625
626 #[test]
627 fn test_header_conversion_roundtrip() {
628 let header = create_header();
629
630 let compressed_header = CompressedHeader::from_header(&header).unwrap();
631
632 let decoded_header = compressed_header.decode_header().unwrap();
633
634 assert_eq!(header.number, decoded_header.number);
635 assert_eq!(header.difficulty, decoded_header.difficulty);
636 assert_eq!(header.timestamp, decoded_header.timestamp);
637 assert_eq!(header.gas_used, decoded_header.gas_used);
638 assert_eq!(header.parent_hash, decoded_header.parent_hash);
639 assert_eq!(header.base_fee_per_gas, decoded_header.base_fee_per_gas);
640 }
641
642 #[test]
643 fn test_block_body_conversion() {
644 let block_body: BlockBody<Bytes> =
645 BlockBody { transactions: vec![], ommers: vec![], withdrawals: None };
646
647 let compressed_body = CompressedBody::from_body(&block_body).unwrap();
648
649 let decoded_body: BlockBody<Bytes> = compressed_body.decode_body().unwrap();
650
651 assert_eq!(decoded_body.transactions.len(), 0);
652 assert_eq!(decoded_body.ommers.len(), 0);
653 assert_eq!(decoded_body.withdrawals, None);
654 }
655
656 #[test]
657 fn test_total_difficulty_roundtrip() {
658 let value = U256::from(123456789u64);
659
660 let total_difficulty = TotalDifficulty::new(value);
661
662 let entry = total_difficulty.to_entry();
663
664 assert_eq!(entry.entry_type, TOTAL_DIFFICULTY);
665
666 let recovered = TotalDifficulty::from_entry(&entry).unwrap();
667
668 assert_eq!(recovered.value, value);
669 }
670
671 #[test]
672 fn test_total_difficulty_ssz_le_encoding() {
673 let value = U256::from(1u64);
676 let td = TotalDifficulty::new(value);
677 let entry = td.to_entry();
678
679 assert_eq!(entry.data[0], 1, "First byte must be 1 (little-endian)");
681 assert_eq!(entry.data[31], 0, "Last byte must be 0 (little-endian)");
682 }
683
684 #[test]
685 fn test_compression_roundtrip() {
686 let rlp_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
687
688 let compressed_header = CompressedHeader::from_rlp(&rlp_data).unwrap();
690 let decompressed = compressed_header.decompress().unwrap();
691 assert_eq!(decompressed, rlp_data);
692
693 let compressed_body = CompressedBody::from_rlp(&rlp_data).unwrap();
695 let decompressed = compressed_body.decompress().unwrap();
696 assert_eq!(decompressed, rlp_data);
697
698 let compressed_receipts = CompressedReceipts::from_rlp(&rlp_data).unwrap();
700 let decompressed = compressed_receipts.decompress().unwrap();
701 assert_eq!(decompressed, rlp_data);
702 }
703
704 #[test]
705 fn test_block_tuple_with_data() {
706 let header = create_header();
708
709 let transactions = vec![Bytes::from(vec![1, 2, 3, 4]), Bytes::from(vec![5, 6, 7, 8])];
710
711 let withdrawals = Some(Withdrawals(vec![]));
712
713 let block_body = BlockBody { transactions, ommers: vec![], withdrawals };
714
715 let block = Block::new(header, block_body);
716
717 let receipts: Vec<u8> = Vec::new();
718
719 let block_tuple =
720 BlockTuple::from_alloy_block(&block, &receipts, U256::from(123456u64)).unwrap();
721
722 let decoded_block: Block<Bytes> = block_tuple.to_alloy_block().unwrap();
724
725 assert_eq!(decoded_block.header.number, 100);
727 assert_eq!(decoded_block.body.transactions.len(), 2);
728 assert_eq!(decoded_block.body.transactions[0], Bytes::from(vec![1, 2, 3, 4]));
729 assert_eq!(decoded_block.body.transactions[1], Bytes::from(vec![5, 6, 7, 8]));
730 assert!(decoded_block.body.withdrawals.is_some());
731 }
732
733 #[test]
734 fn test_single_receipt_compression_roundtrip() {
735 let test_receipt = create_test_receipt(TxType::Eip1559, true, 21000, 2);
736
737 let compressed_receipts =
739 CompressedReceipts::from_encodable(&test_receipt).expect("Failed to compress receipt");
740
741 assert!(!compressed_receipts.data.is_empty());
743
744 let decoded_receipt: Receipt =
746 compressed_receipts.decode().expect("Failed to decode compressed receipt");
747
748 assert_eq!(decoded_receipt.tx_type, test_receipt.tx_type);
750 assert_eq!(decoded_receipt.success, test_receipt.success);
751 assert_eq!(decoded_receipt.cumulative_gas_used, test_receipt.cumulative_gas_used);
752 assert_eq!(decoded_receipt.logs.len(), test_receipt.logs.len());
753
754 for (original_log, decoded_log) in test_receipt.logs.iter().zip(decoded_receipt.logs.iter())
756 {
757 assert_eq!(decoded_log.address, original_log.address);
758 assert_eq!(decoded_log.data.topics(), original_log.data.topics());
759 }
760 }
761
762 #[test]
763 fn test_accumulator_from_header_records_known_vectors() {
764 let expected_empty: B256 =
767 "4a8c3a07c8d23adc5bac61157555c3c784d53d9bc110c1370809bd23cd93777d".parse().unwrap();
768 let expected_single_zero: B256 =
769 "81fd641249670887a731386e756a7a1538dc781b1b0bf016889045d350812817".parse().unwrap();
770 let expected_single_nonzero: B256 =
771 "ada35c48d81117f4fd588554cd4c4752356336e84cb41106dea1ceb4cfac8799".parse().unwrap();
772
773 let acc_empty = Accumulator::from_header_records(&[]).unwrap();
775 assert_eq!(acc_empty.root, expected_empty);
776
777 let records = vec![HeaderRecord { block_hash: B256::ZERO, total_difficulty: U256::ZERO }];
779 let acc = Accumulator::from_header_records(&records).unwrap();
780 assert_eq!(acc.root, expected_single_zero);
781
782 let records2 = vec![HeaderRecord {
784 block_hash: B256::from([1u8; 32]),
785 total_difficulty: U256::from(100u64),
786 }];
787 let acc2 = Accumulator::from_header_records(&records2).unwrap();
788 assert_eq!(acc2.root, expected_single_nonzero);
789 }
790
791 #[test]
792 fn test_accumulator_rejects_oversized_input() {
793 let records = vec![
794 HeaderRecord { block_hash: B256::ZERO, total_difficulty: U256::ZERO };
795 MAX_BLOCKS_PER_ERA1 + 1
796 ];
797 assert!(Accumulator::from_header_records(&records).is_err());
798 }
799
800 #[test]
801 fn test_receipt_list_compression() {
802 let receipts = create_test_receipts();
803
804 let compressed_receipts = CompressedReceipts::from_encodable_list(&receipts)
806 .expect("Failed to compress receipt list");
807
808 let decoded_receipts: Vec<Receipt> =
812 compressed_receipts.decode().expect("Failed to decode compressed receipt list");
813
814 assert_eq!(decoded_receipts.len(), receipts.len());
816
817 for (original, decoded) in receipts.iter().zip(decoded_receipts.iter()) {
818 assert_eq!(decoded.tx_type, original.tx_type);
819 assert_eq!(decoded.success, original.success);
820 assert_eq!(decoded.cumulative_gas_used, original.cumulative_gas_used);
821 assert_eq!(decoded.logs.len(), original.logs.len());
822
823 for (original_log, decoded_log) in original.logs.iter().zip(decoded.logs.iter()) {
824 assert_eq!(decoded_log.address, original_log.address);
825 assert_eq!(decoded_log.data.topics(), original_log.data.topics());
826 }
827 }
828 }
829}