calldata_compressor/
compressor.rs

1#![allow(clippy::must_use_candidate)]
2#![allow(clippy::missing_const_for_fn)]
3#![allow(clippy::cast_possible_wrap)]
4#![allow(clippy::missing_errors_doc)]
5#![allow(clippy::too_many_lines)]
6#![allow(clippy::missing_panics_doc)]
7#![allow(clippy::cast_possible_truncation)]
8#![allow(clippy::cast_sign_loss)]
9use std::collections::HashMap;
10
11use alloy::primitives::Bytes;
12use num_bigint::BigUint;
13use rayon::prelude::*;
14
15use crate::errors::CompressorError;
16
17pub type Bytes32 = [u8; 32];
18
19/// How to compress a specific portion of data
20#[derive(Debug, Clone)]
21pub struct CompressDataDescription {
22    pub start_byte: usize,   // starting byte index of the data portion to compress
23    pub amount_bytes: usize, // number of bytes to compress starting from start_byte
24    pub method: u8,          // compression method(decompress mask) to use
25}
26
27impl CompressDataDescription {
28    pub fn new(start_byte: usize, amount_bytes: usize, method: u8) -> Self {
29        Self {
30            start_byte,
31            amount_bytes,
32            method,
33        }
34    }
35}
36
37/// the power of the compressed data
38#[derive(Debug, Clone, Default)]
39pub struct CompressDataPower {
40    pub decompressed_size: usize, // the size of the original(decompressed) data in bytes.
41    pub compressed_size: usize,   // the size of the compressed data in bytes.
42}
43
44impl CompressDataPower {
45    pub fn new(decompressed_size: usize, compressed_size: usize) -> Self {
46        Self {
47            decompressed_size,
48            compressed_size,
49        }
50    }
51
52    // the difference between the original(decompressed) data size and the compressed data size.
53    pub fn range(&self) -> i64 {
54        self.decompressed_size as i64 - self.compressed_size as i64
55    }
56
57    // adds the decompressed_size and compressed_size of another CompressDataPower instance to the current instance
58    pub fn add(&mut self, other: &Self) {
59        self.decompressed_size += other.decompressed_size;
60        self.compressed_size += other.compressed_size;
61    }
62}
63
64/// the compressed data itself, along with its description and power
65#[derive(Default, Debug, Clone)]
66pub struct CompressData {
67    pub power: CompressDataPower, /* An instance of CompressDataPower representing the power of the compressed data. */
68    pub descriptions: Vec<CompressDataDescription>, /* An instance of CompressDataDescription representing the description of how the data was compressed. */
69}
70
71impl CompressData {
72    pub fn new(power: CompressDataPower, descriptions: Vec<CompressDataDescription>) -> Self {
73        Self {
74            power,
75            descriptions,
76        }
77    }
78}
79
80#[derive(Default, Debug, Clone)]
81pub struct ByteInfo {
82    pub index: usize,
83    pub zero_compress: CompressDataPower,
84    pub copy_compress: CompressDataPower,
85    pub storage_compress: Vec<CompressDataPower>,
86}
87
88impl ByteInfo {
89    pub fn new(
90        index: usize,
91        zero_compress: CompressDataPower,
92        copy_compress: CompressDataPower,
93        storage_compress: Vec<CompressDataPower>,
94    ) -> Self {
95        Self {
96            index,
97            zero_compress,
98            copy_compress,
99            storage_compress,
100        }
101    }
102}
103
104/// provide a tool to compress a hex string representing a smart contract call.
105/// The compression is done by
106#[derive(Debug, Clone)]
107pub struct Calldata {
108    pub data: Bytes,
109    pub wallet_addr: Bytes32,
110    pub contract_addr: Bytes32,
111    pub bytes_info: Vec<ByteInfo>,
112    pub dict: Vec<Bytes32>,              // contract dict data
113    pub lookup: HashMap<Vec<u8>, usize>, // value -> index
114}
115
116impl Calldata {
117    pub fn new(
118        data: Bytes,
119        wallet_addr: Bytes32,
120        contract_addr: Bytes32,
121    ) -> Result<Self, CompressorError> {
122        let len = data.len();
123        Ok(Self {
124            data,
125            wallet_addr,
126            contract_addr,
127            bytes_info: vec![ByteInfo::default(); len],
128            dict: Vec::new(),
129            lookup: HashMap::new(),
130        })
131    }
132
133    pub fn analyse(&mut self) {
134        for i in 0..self.data.len() {
135            self.bytes_info[i] = ByteInfo {
136                index: i,
137                zero_compress: self.check_zeros_case(i),
138                copy_compress: self.check_copy_case_with_zeros(i),
139                storage_compress: self.check_storage_case(i).unwrap_or_default(),
140            };
141        }
142    }
143
144    pub fn create_desc(
145        &self,
146        from_byte: usize,
147        array_desc: &[CompressDataDescription],
148        amount_bytes: usize,
149        method: u8,
150    ) -> CompressDataDescription {
151        let start_byte: usize = if array_desc.is_empty() {
152            from_byte
153        } else {
154            let prev_desc_index = array_desc.len() - 1;
155            array_desc[prev_desc_index].start_byte + array_desc[prev_desc_index].amount_bytes
156        };
157        CompressDataDescription {
158            start_byte,
159            amount_bytes,
160            method,
161        }
162    }
163
164    pub fn add_just_copy_compress(
165        &self,
166        from_byte: usize,
167        mut result_compress: CompressData,
168        amount: usize,
169    ) -> CompressData {
170        if amount != 0 {
171            result_compress.power.add(&CompressDataPower {
172                decompressed_size: amount,
173                compressed_size: 1 + amount,
174            });
175            result_compress.descriptions.push(self.create_desc(
176                from_byte,
177                &result_compress.descriptions,
178                amount,
179                0x01,
180            ));
181        }
182        result_compress
183    }
184
185    pub fn compress_part(&self, from_byte: usize, to_byte: usize) -> CompressData {
186        let mut part_compress = CompressData {
187            power: CompressDataPower {
188                decompressed_size: 0,
189                compressed_size: 0,
190            },
191            descriptions: vec![],
192        };
193        let mut just_copy_amount: usize = 0;
194
195        let mut i = from_byte;
196        while i <= to_byte {
197            if self.bytes_info[i].zero_compress.decompressed_size > to_byte - i {
198                part_compress =
199                    self.add_just_copy_compress(from_byte, part_compress, just_copy_amount);
200                part_compress.power.add(&CompressDataPower {
201                    decompressed_size: to_byte - from_byte + 1,
202                    compressed_size: 1,
203                });
204                part_compress.descriptions.push(CompressDataDescription {
205                    start_byte: i,
206                    amount_bytes: to_byte - i + 1,
207                    method: 0x00,
208                });
209                return part_compress;
210            }
211
212            let mut zero_bytes_amount = 0;
213            let mut is_padding_with_copy = false;
214            let mut need_just_copy_amount = true;
215
216            if self.bytes_info[i].zero_compress.decompressed_size != 0 {
217                if self.bytes_info[i].copy_compress.decompressed_size > to_byte - i
218                    || self.bytes_info[i].zero_compress.range()
219                        > self.bytes_info[i].copy_compress.range()
220                {
221                    zero_bytes_amount = self.bytes_info[i].zero_compress.decompressed_size;
222                } else {
223                    is_padding_with_copy = true;
224                }
225            }
226            let mut is_storage_compress_used: bool = false;
227            let is_zero_compress: bool = zero_bytes_amount > 0;
228            for j in 0..self.bytes_info[i].storage_compress.len() {
229                if self.bytes_info[i].storage_compress[j].decompressed_size <= to_byte - i + 1 {
230                    let is_storage_range_more_than_copy_compress =
231                        self.bytes_info[i].storage_compress[j].range()
232                            > self.bytes_info[i].copy_compress.range();
233
234                    if !is_zero_compress
235                        && !is_storage_range_more_than_copy_compress
236                        && !is_padding_with_copy
237                    {
238                        continue;
239                    }
240
241                    part_compress =
242                        self.add_just_copy_compress(from_byte, part_compress, just_copy_amount);
243
244                    if is_zero_compress {
245                        if self.bytes_info[i].storage_compress[j].range()
246                            > self.bytes_info[i].zero_compress.range()
247                        {
248                            part_compress
249                                .power
250                                .add(&self.bytes_info[i].storage_compress[j]);
251                            part_compress.descriptions.push(self.create_desc(
252                                from_byte,
253                                &part_compress.descriptions,
254                                self.bytes_info[i].storage_compress[j].decompressed_size,
255                                if self.bytes_info[i].storage_compress[j].compressed_size == 2 {
256                                    0x10
257                                } else {
258                                    0x11
259                                },
260                            ));
261                            i += self.bytes_info[i].storage_compress[j].decompressed_size;
262                        } else {
263                            part_compress.power.add(&self.bytes_info[i].zero_compress);
264                            part_compress.descriptions.push(self.create_desc(
265                                from_byte,
266                                &part_compress.descriptions,
267                                zero_bytes_amount,
268                                0x00,
269                            ));
270                            i += zero_bytes_amount;
271                        }
272                    } else if is_storage_range_more_than_copy_compress {
273                        part_compress
274                            .power
275                            .add(&self.bytes_info[i].storage_compress[j]);
276                        part_compress.descriptions.push(self.create_desc(
277                            from_byte,
278                            &part_compress.descriptions,
279                            self.bytes_info[i].storage_compress[j].decompressed_size,
280                            if self.bytes_info[i].storage_compress[j].compressed_size == 2 {
281                                0x10
282                            } else {
283                                0x11
284                            },
285                        ));
286                        i += self.bytes_info[i].storage_compress[j].decompressed_size;
287                    } else if is_padding_with_copy {
288                        part_compress.power.add(&self.bytes_info[i].copy_compress);
289                        part_compress.descriptions.push(self.create_desc(
290                            from_byte,
291                            &part_compress.descriptions,
292                            self.bytes_info[i].copy_compress.decompressed_size,
293                            0x01,
294                        ));
295                        i += self.bytes_info[i].copy_compress.decompressed_size;
296                    }
297
298                    just_copy_amount = 0;
299                    need_just_copy_amount = false;
300                    is_storage_compress_used = true;
301                    break;
302                }
303            }
304
305            if !is_storage_compress_used {
306                if is_zero_compress || is_padding_with_copy {
307                    part_compress =
308                        self.add_just_copy_compress(from_byte, part_compress, just_copy_amount);
309                }
310
311                if is_zero_compress {
312                    part_compress.power.add(&self.bytes_info[i].zero_compress);
313                    part_compress.descriptions.push(self.create_desc(
314                        from_byte,
315                        &part_compress.descriptions,
316                        zero_bytes_amount,
317                        0x00,
318                    ));
319                    i += zero_bytes_amount;
320                } else if is_padding_with_copy {
321                    part_compress.power.add(&self.bytes_info[i].copy_compress);
322                    part_compress.descriptions.push(self.create_desc(
323                        from_byte,
324                        &part_compress.descriptions,
325                        self.bytes_info[i].copy_compress.decompressed_size,
326                        0x01,
327                    ));
328                    i += self.bytes_info[i].copy_compress.decompressed_size;
329                }
330
331                if is_zero_compress || is_padding_with_copy {
332                    just_copy_amount = 0;
333                    need_just_copy_amount = false;
334                }
335            }
336            if need_just_copy_amount {
337                let new_just_copy_amount = std::cmp::min(
338                    self.bytes_info[i].copy_compress.decompressed_size,
339                    to_byte - i + 1,
340                );
341                just_copy_amount += new_just_copy_amount;
342                if just_copy_amount > 32 {
343                    part_compress = self.add_just_copy_compress(from_byte, part_compress, 32);
344                    just_copy_amount -= 32;
345                }
346                i += new_just_copy_amount;
347            }
348        }
349
350        part_compress = self.add_just_copy_compress(from_byte, part_compress, just_copy_amount);
351
352        part_compress
353    }
354
355    pub fn zip(
356        &self,
357        descriptions: &[CompressDataDescription],
358    ) -> Result<Vec<u8>, CompressorError> {
359        let mut result: Vec<u8> = Vec::new();
360        let bb = [32, 20, 4, 31];
361        for description in descriptions {
362            match description.method {
363                0x00 => {
364                    // 00XXXXXX
365                    result.push((description.amount_bytes - 1) as u8);
366                }
367                0x01 => {
368                    // 01PXXXXX
369                    let copy_bytes =
370                        self.get_bytes(description.start_byte, description.amount_bytes)?;
371                    let mut non_zero_byte_index = 0;
372                    for (j, _) in copy_bytes.iter().enumerate().take(description.amount_bytes) {
373                        if copy_bytes[j] != 0x00 {
374                            non_zero_byte_index = j;
375                            break;
376                        }
377                    }
378                    result.push(
379                        ((description.amount_bytes - non_zero_byte_index - 1)
380                            + 64
381                            + if non_zero_byte_index == 0 { 0 } else { 32 })
382                            as u8,
383                    );
384                    let copy_bytes = self.get_bytes(
385                        description.start_byte + non_zero_byte_index,
386                        description.amount_bytes - non_zero_byte_index,
387                    )?;
388                    result.extend(copy_bytes);
389                }
390                0x10 => {
391                    // 10BBXXXX XXXXXXXX
392                    let index = *self
393                        .lookup
394                        .get(self.get_bytes(description.start_byte, description.amount_bytes)?)
395                        .ok_or(CompressorError::LookupNotFound)?;
396                    result.extend(
397                        BigUint::from(
398                            index
399                                + 2_u64.pow(15) as usize
400                                + (bb
401                                    .par_iter()
402                                    .position_first(|&r| r == description.amount_bytes)
403                                    .unwrap()
404                                    * 2_u64.pow(12) as usize),
405                        )
406                        .to_bytes_be(),
407                    );
408                }
409                0x11 => {
410                    // 11BBXXXX XXXXXXXX XXXXXXXX
411                    let index = *self
412                        .lookup
413                        .get(self.get_bytes(description.start_byte, description.amount_bytes)?)
414                        .ok_or(CompressorError::LookupNotFound)?;
415                    result.extend(
416                        BigUint::from(
417                            index
418                                + 3 * 2_u64.pow(22) as usize
419                                + (bb
420                                    .par_iter()
421                                    .position_first(|&r| r == description.amount_bytes)
422                                    .unwrap()
423                                    * 2_u64.pow(20) as usize),
424                        )
425                        .to_bytes_be(),
426                    );
427                }
428                _ => {
429                    return Err(CompressorError::UnsupportedMethod(description.method));
430                }
431            }
432        }
433        Ok(result)
434    }
435
436    pub fn compress(&mut self) -> Result<CompressResult, CompressorError> {
437        self.analyse();
438        let mut best_compress_for_first_n_bytes: Vec<CompressData> =
439            vec![CompressData::default(); self.bytes_info.len()];
440
441        if self.bytes_info[0].zero_compress.decompressed_size != 0 {
442            best_compress_for_first_n_bytes[0] = CompressData {
443                power: CompressDataPower {
444                    decompressed_size: 1,
445                    compressed_size: 1,
446                },
447                descriptions: vec![CompressDataDescription {
448                    start_byte: 0,
449                    amount_bytes: 1,
450                    method: 0x00,
451                }],
452            };
453        } else {
454            best_compress_for_first_n_bytes[0] = CompressData {
455                power: CompressDataPower {
456                    decompressed_size: 1,
457                    compressed_size: 2,
458                },
459                descriptions: vec![CompressDataDescription {
460                    start_byte: 0,
461                    amount_bytes: 1,
462                    method: 0x01,
463                }],
464            };
465        }
466
467        for i in 1..self.bytes_info.len() {
468            best_compress_for_first_n_bytes[i] = CompressData {
469                power: CompressDataPower {
470                    decompressed_size: best_compress_for_first_n_bytes[i - 1]
471                        .power
472                        .decompressed_size
473                        + 1,
474                    compressed_size: best_compress_for_first_n_bytes[i - 1].power.compressed_size
475                        + 2,
476                },
477                descriptions: [
478                    best_compress_for_first_n_bytes[i - 1].descriptions.clone(),
479                    vec![CompressDataDescription {
480                        start_byte: i,
481                        amount_bytes: 1,
482                        method: 0x01,
483                    }],
484                ]
485                .concat(),
486            };
487
488            for j in (std::cmp::max(0, i as isize - 63) as usize..=i).rev() {
489                let part_compress = self.compress_part(j, i);
490
491                let mut prefix_compress = CompressData {
492                    power: CompressDataPower::default(),
493                    descriptions: Vec::new(),
494                };
495
496                if part_compress.descriptions[0].start_byte != 0 {
497                    let prev_desc_index = part_compress.descriptions[0].start_byte - 1;
498                    prefix_compress.power = best_compress_for_first_n_bytes[prev_desc_index]
499                        .power
500                        .clone();
501                    prefix_compress.descriptions = best_compress_for_first_n_bytes[prev_desc_index]
502                        .descriptions
503                        .clone();
504                }
505
506                if prefix_compress.power.range() + part_compress.power.range()
507                    > best_compress_for_first_n_bytes[i].power.range()
508                {
509                    best_compress_for_first_n_bytes[i] = CompressData {
510                        power: CompressDataPower {
511                            decompressed_size: prefix_compress.power.decompressed_size
512                                + part_compress.power.decompressed_size,
513                            compressed_size: prefix_compress.power.compressed_size
514                                + part_compress.power.compressed_size,
515                        },
516                        descriptions: [prefix_compress.descriptions, part_compress.descriptions]
517                            .concat(),
518                    };
519                }
520            }
521
522            // best_compress_for_first_n_bytes.push(current_best_compress);
523        }
524
525        Ok(CompressResult {
526            uncompressed_data: self.data.clone(),
527            compressed_data: Bytes::from(
528                self.zip(
529                    &best_compress_for_first_n_bytes
530                        .last()
531                        .unwrap()
532                        .descriptions
533                        .clone(),
534                )?,
535            ),
536            power: best_compress_for_first_n_bytes
537                .last()
538                .unwrap()
539                .power
540                .clone(),
541            description: best_compress_for_first_n_bytes
542                .last()
543                .unwrap()
544                .descriptions
545                .clone(),
546        })
547    }
548
549    pub fn get_byte(&self, n: usize) -> Result<&u8, CompressorError> {
550        self.data.get(n).ok_or(CompressorError::InvalidRange)
551    }
552
553    pub fn get_bytes(&self, start: usize, n: usize) -> Result<&[u8], CompressorError> {
554        let end = std::cmp::min(start + n, self.data.len());
555        if start >= end {
556            return Err(CompressorError::InvalidRange);
557        }
558        self.data
559            .get(start..end)
560            .ok_or(CompressorError::InvalidRange)
561    }
562
563    pub fn init_dict(&mut self, dict: &[Bytes32]) {
564        let mut dict_data = vec![self.wallet_addr, self.contract_addr];
565        dict_data.extend(dict);
566        self.dict = dict_data;
567
568        self.dict.iter().enumerate().for_each(|(i, data)| {
569            let value: Vec<u8> = data.to_vec();
570            self.lookup.insert(value.clone(), i);
571            self.lookup.insert(value[value.len() - 4..].to_vec(), i);
572            self.lookup.insert(value[value.len() - 20..].to_vec(), i);
573            self.lookup.insert(value[value.len() - 31..].to_vec(), i);
574        });
575    }
576
577    // 00XXXXXX
578    pub fn check_zeros_case(&self, n: usize) -> CompressDataPower {
579        let mut current_byte_index = n;
580        let byte = self.get_byte(current_byte_index);
581        if !byte.is_ok_and(|x| *x == 0x00) {
582            return CompressDataPower {
583                decompressed_size: 0,
584                compressed_size: 0,
585            };
586        }
587        current_byte_index += 1;
588        // 00XXXXXX case, XXXXXX max value is 2**6-1=63
589        while self.get_byte(current_byte_index).is_ok_and(|x| *x == 0x00)
590            && current_byte_index < self.data.len()
591            && current_byte_index - n <= 63
592        {
593            current_byte_index += 1;
594        }
595        CompressDataPower {
596            decompressed_size: current_byte_index - n,
597            compressed_size: 1,
598        }
599    }
600
601    // 01PXXXXX
602    pub fn check_copy_case_with_zeros(&self, n: usize) -> CompressDataPower {
603        let mut current_byte_index = n;
604        let byte = self.get_byte(current_byte_index);
605        if !byte.is_ok_and(|x| *x == 0x00) {
606            // decompressed: 0xXX, 1 Byte
607            // compressed: 01000000 0xXX, 2 Byte
608            return CompressDataPower {
609                decompressed_size: 1,
610                compressed_size: 2,
611            };
612        }
613        current_byte_index += 1;
614        // 01PXXXXX case, XXXXX max value is 2**5-1=31
615        while self.get_byte(current_byte_index).is_ok_and(|x| *x == 0x00)
616            && current_byte_index < self.data.len()
617        {
618            if current_byte_index - n == 32 {
619                return CompressDataPower {
620                    decompressed_size: 31,
621                    compressed_size: 32,
622                };
623            }
624            current_byte_index += 1;
625        }
626        let decompressed_bytes_amount = std::cmp::min(self.data.len() - n, 32);
627        CompressDataPower {
628            decompressed_size: decompressed_bytes_amount,
629            compressed_size: if decompressed_bytes_amount == 32 {
630                1 + 32 - (current_byte_index - n + 1)
631            } else {
632                1 + decompressed_bytes_amount
633            },
634        }
635    }
636
637    // 10BBXXXX XXXXXXXX case and 11BBXXXX XXXXXXXX XXXXXXXX case
638    pub fn check_storage_case(&self, n: usize) -> Result<Vec<CompressDataPower>, CompressorError> {
639        if self.dict.is_empty() || self.lookup.is_empty() {
640            return Err(CompressorError::DictNotInit);
641        }
642
643        let mut best = Vec::<CompressDataPower>::new();
644        for len in &[32, 31, 20, 4] {
645            let tail = self.get_bytes(n, *len).unwrap();
646            let index = self.lookup.get(tail);
647            if let Some(index) = index {
648                if tail.len() >= *len {
649                    best.push(CompressDataPower {
650                        decompressed_size: *len,
651                        compressed_size: if *index > 4096 { 3 } else { 2 }, // 11BBXXXX XXXXXXXX XXXXXXXX or 10BBXXXX XXXXXXXX
652                    });
653                }
654            }
655        }
656        Ok(best)
657    }
658}
659
660pub struct CompressResult {
661    pub uncompressed_data: Bytes,
662    pub compressed_data: Bytes,
663    pub power: CompressDataPower,
664    pub description: Vec<CompressDataDescription>,
665}
666
667pub fn compress(
668    calldata: Bytes,
669    wallet_addr: Bytes32,
670    contract_addr: Bytes32,
671    dict: &[Bytes32],
672) -> Result<CompressResult, CompressorError> {
673    let mut calldata = Calldata::new(calldata, wallet_addr, contract_addr).unwrap();
674    calldata.init_dict(dict);
675    calldata.compress()
676}
677
678#[cfg(test)]
679mod tests {
680    use std::{fs::File, io::Read, str::FromStr};
681
682    use serde::Deserialize;
683
684    use super::*;
685    use crate::assert_json_eq;
686    #[allow(dead_code)]
687    #[derive(Debug, Deserialize)]
688    struct TestData {
689        pub compress: String,
690        pub uncompress: String,
691    }
692
693    fn read_calldata_file(file_path: &str) -> Result<TestData, Box<dyn std::error::Error>> {
694        let mut file = File::open(file_path)?;
695
696        let mut contents = String::new();
697        file.read_to_string(&mut contents)?;
698
699        let data: TestData = serde_json::from_str(&contents)?;
700
701        Ok(data)
702    }
703
704    fn read_json_file(file_path: &str) -> Result<String, Box<dyn std::error::Error>> {
705        let mut file = File::open(file_path)?;
706
707        let mut contents = String::new();
708        file.read_to_string(&mut contents)?;
709
710        Ok(contents)
711    }
712
713    #[test]
714    fn test_compress_big() {
715        // 1000 zero Bytes32 vector
716        let empty_dict = vec![Bytes32::default(); 1];
717        let test_data = read_calldata_file("test-data/calldata.json").unwrap();
718        let calldata = test_data.uncompress.strip_prefix("0x").unwrap();
719        let expected_compress = test_data.compress.strip_prefix("0x").unwrap();
720        let calldata = Bytes::from(hex::decode(calldata).unwrap());
721        let wallet_addr = Bytes32::default();
722        let contract_addr = Bytes32::default();
723        let result = compress(calldata, wallet_addr, contract_addr, &empty_dict);
724        assert!(result.is_ok());
725        assert_eq!(
726            hex::encode(result.unwrap().compressed_data.to_vec()),
727            expected_compress
728        );
729    }
730
731    #[test]
732    fn test_compress_small() {
733        let empty_dict = vec![Bytes32::default(); 1];
734        let calldata = "0xf433d35e04bf7fea9df9ef9f80d4a91a3c3dec84540583b7103c7a69f7bbd4b7585ef752847ebec11584e73282b6dec46dd8ea6464d69f4003581960f39d8492000000000000000000000000000000000000000000000000000000000000001b0000000000000000000000000000000000000000000000000000000000003a13000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000258220761ade872c94f85e62f1b24a74eec792aaa3677b6201071fd05c1698e89000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000033390598000000000000000000000000000000000000000000000000000000000010f7df4000000000000000000000000000000000000000000000000000000000001cab680000000000000000000000000000000000000000000000000000dcc54f790800000000000000000000000000000000000000000000000000000000000001ba1700000000000000000000000000000000000000000000000017ac92ba438492fe0000000000000000000000000000000000000000000000000000018d2f8b7e8858220761ade872c94f85e62f1b24a74eec792aaa3677b6201071fd05c1698e89000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffccc6fa68000000000000000000000000000000000000000000000000000000000010f7df4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dcc54f790800000000000000000000000000000000000000000000000000000000000001ba1800000000000000000000000000000000000000000000000017ac92ba438492fe0000000000000000000000000000000000000000000000000000018d2f8b7e88".strip_prefix("0x").unwrap();
735        let calldata = Bytes::from(hex::decode(calldata).unwrap());
736        let wallet_addr = Bytes32::default();
737        let contract_addr = Bytes32::default();
738
739        let mut cb = Calldata::new(calldata.clone(), wallet_addr, contract_addr).unwrap();
740        cb.init_dict(&empty_dict);
741        cb.analyse();
742
743        let mut zero_compresses: Vec<[usize; 2]> = vec![];
744        let mut copy_compress: Vec<[usize; 2]> = vec![];
745        for info in cb.bytes_info {
746            zero_compresses.push([
747                info.zero_compress.decompressed_size,
748                info.zero_compress.compressed_size,
749            ]);
750            copy_compress.push([
751                info.copy_compress.decompressed_size,
752                info.copy_compress.compressed_size,
753            ]);
754        }
755        // zero_compresses to json string
756        let zero_compresses_json = serde_json::to_string(&zero_compresses).unwrap();
757        let copy_compress_json = serde_json::to_string(&copy_compress).unwrap();
758        let expected_zero_compress = read_json_file("test-data/zero_compress.json").unwrap();
759        assert_json_eq!(&zero_compresses_json, &expected_zero_compress);
760        let expected_copy_compress = read_json_file("test-data/copy_compress.json").unwrap();
761        assert_json_eq!(&copy_compress_json, &expected_copy_compress);
762
763        let result = compress(calldata, wallet_addr, contract_addr, &empty_dict);
764        assert!(result.is_ok());
765        assert_eq!(
766            hex::encode(result.unwrap().compressed_data.to_vec()),
767         "40f45f33d35e04bf7fea9df9ef9f80d4a91a3c3dec84540583b7103c7a69f7bbd4b7585e5ef752847ebec11584e73282b6dec46dd8ea6464d69f4003581960f39d849200611b001c413a13006102001d40c0006102585e220761ade872c94f85e62f1b24a74eec792aaa3677b6201071fd05c1698e8900610f00194333390598006310f7df4000631cab68001844dcc54f7908006201ba17006817ac92ba438492fe000067018d2f8b7e8858225d0761ade872c94f85e62f1b24a74eec792aaa3677b6201071fd05c1698e8900610fff5dffffffffffffffffffffffffffffffffffffffffffffffffffffccc6fa68006310f7df40003844dcc54f7908006201ba18006817ac92ba438492fe001845018d2f8b7e88"
768        );
769    }
770}