hopr_primitive_types/
primitives.rs

1use chrono::serde::ts_seconds_option;
2use chrono::{DateTime, Utc};
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5use sha3::Digest;
6use std::cmp::Ordering;
7use std::fmt::{Debug, Display, Formatter};
8use std::ops::{Add, Mul, Sub};
9use std::str::FromStr;
10
11use crate::errors::{GeneralError, GeneralError::InvalidInput, GeneralError::ParseError, Result};
12use crate::prelude::BytesRepresentable;
13use crate::traits::{IntoEndian, ToHex, UnitaryFloatOps};
14
15pub type U256 = primitive_types::U256;
16
17/// Represents an Ethereum address
18#[derive(Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize, Hash, PartialOrd, Ord)]
19pub struct Address([u8; Self::SIZE]);
20
21impl Debug for Address {
22    // Intentionally same as Display
23    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24        write!(f, "{}", self.to_hex())
25    }
26}
27
28impl Display for Address {
29    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30        write!(f, "{}", self.to_hex())
31    }
32}
33
34impl Address {
35    pub fn new(bytes: &[u8]) -> Self {
36        assert_eq!(bytes.len(), Self::SIZE, "invalid length");
37        let mut ret = Self::default();
38        ret.0.copy_from_slice(bytes);
39        ret
40    }
41
42    pub fn to_bytes32(&self) -> Box<[u8]> {
43        let mut ret = Vec::with_capacity(12 + Self::SIZE);
44        ret.extend_from_slice(&[0u8; 12]);
45        ret.extend_from_slice(&self.0);
46        ret.into_boxed_slice()
47    }
48
49    /// Checks if the address is all zeroes.
50    pub fn is_zero(&self) -> bool {
51        self.0.iter().all(|e| 0_u8.eq(e))
52    }
53
54    /// Turns the address into a checksum-ed address string
55    /// according to https://eips.ethereum.org/EIPS/eip-55>
56    pub fn to_checksum(&self) -> String {
57        let address_hex = hex::encode(self.0);
58
59        let hash = sha3::Keccak256::digest(address_hex.as_bytes());
60
61        let mut ret = String::with_capacity(Self::SIZE * 2 + 2);
62        ret.push_str("0x");
63
64        for (i, c) in address_hex.chars().enumerate() {
65            let nibble = (hash[i / 2] >> (((i + 1) % 2) * 4)) & 0xf;
66            if nibble < 8 {
67                ret.push(c);
68            } else {
69                ret.push(c.to_ascii_uppercase());
70            }
71        }
72        ret
73    }
74}
75
76impl AsRef<[u8]> for Address {
77    fn as_ref(&self) -> &[u8] {
78        &self.0
79    }
80}
81
82impl TryFrom<&[u8]> for Address {
83    type Error = GeneralError;
84
85    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
86        Ok(Self(value.try_into().map_err(|_| ParseError("Address".into()))?))
87    }
88}
89
90impl BytesRepresentable for Address {
91    /// Fixed size of the address when encoded as bytes (e.g., via `as_ref()`).
92    const SIZE: usize = 20;
93}
94
95impl From<[u8; Address::SIZE]> for Address {
96    fn from(value: [u8; Address::SIZE]) -> Self {
97        Self(value)
98    }
99}
100
101impl From<Address> for [u8; Address::SIZE] {
102    fn from(value: Address) -> Self {
103        value.0
104    }
105}
106
107impl From<primitive_types::H160> for Address {
108    fn from(value: primitive_types::H160) -> Self {
109        Self(value.0)
110    }
111}
112
113impl From<Address> for primitive_types::H160 {
114    fn from(value: Address) -> Self {
115        primitive_types::H160::from_slice(&value.0)
116    }
117}
118
119impl FromStr for Address {
120    type Err = GeneralError;
121
122    fn from_str(value: &str) -> Result<Address> {
123        Self::from_hex(value)
124    }
125}
126
127/// Represents a type of the balance: native or HOPR tokens.
128#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString)]
129pub enum BalanceType {
130    /// Native tokens of the underlying chain.
131    Native,
132    /// HOPR tokens.
133    HOPR,
134}
135
136impl BalanceType {
137    /// Creates [Balance] of 1 of this type.
138    pub fn one(self) -> Balance {
139        self.balance(1)
140    }
141
142    /// Creates zero [Balance] of this type.
143    pub fn zero(self) -> Balance {
144        self.balance(0)
145    }
146
147    /// Creates [Balance] of the given `amount` of this type.
148    pub fn balance<T: Into<U256>>(self, amount: T) -> Balance {
149        Balance::new(amount, self)
150    }
151
152    /// Deserializes the given amount and creates a new [Balance] instance.
153    /// The bytes are assumed to be in Big Endian order.
154    /// The method panics if more than 32 `bytes` were given.
155    pub fn balance_bytes<T: AsRef<[u8]>>(self, bytes: T) -> Balance {
156        Balance::new(U256::from_be_bytes(bytes), self)
157    }
158}
159
160/// Represents balance of some coin or token.
161#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162pub struct Balance(U256, BalanceType);
163
164impl Balance {
165    /// Number of digits in the base unit
166    pub const SCALE: usize = 19;
167
168    /// Creates a new balance given the value and type
169    pub fn new<T: Into<U256>>(value: T, balance_type: BalanceType) -> Self {
170        Self(value.into(), balance_type)
171    }
172
173    /// Creates new balance of the given type from the base 10 integer string
174    pub fn new_from_str(value: &str, balance_type: BalanceType) -> Self {
175        Self(
176            U256::from_dec_str(value).unwrap_or_else(|_| panic!("invalid decimal number {value}")),
177            balance_type,
178        )
179    }
180
181    /// Creates zero balance of the given type
182    pub fn zero(balance_type: BalanceType) -> Self {
183        Self(U256::zero(), balance_type)
184    }
185
186    /// Retrieves the type (symbol) of the balance
187    pub fn balance_type(&self) -> BalanceType {
188        self.1
189    }
190
191    /// Creates balance of the given value with the same symbol
192    pub fn of_same(&self, value: &str) -> Self {
193        Self::new_from_str(value, self.1)
194    }
195
196    pub fn amount(&self) -> U256 {
197        self.0
198    }
199
200    pub fn is_zero(&self) -> bool {
201        self.0.is_zero()
202    }
203
204    pub fn amount_base_units(&self) -> String {
205        let val = self.0.to_string();
206
207        match val.len().cmp(&Self::SCALE) {
208            Ordering::Greater => {
209                let (l, r) = val.split_at(val.len() - Self::SCALE + 1);
210                format!("{l}.{}", &r[..r.len() - (val.len() - Self::SCALE)],)
211            }
212            Ordering::Less => format!("0.{empty:0>width$}", empty = &val, width = Self::SCALE - 1,),
213            Ordering::Equal => {
214                let (l, r) = val.split_at(1);
215                format!("{l}.{r}")
216            }
217        }
218    }
219
220    pub fn to_formatted_string(&self) -> String {
221        format!("{} {}", self.amount_base_units(), self.1)
222    }
223
224    pub fn to_value_string(&self) -> String {
225        self.0.to_string()
226    }
227}
228
229impl<T: Into<U256>> From<(T, BalanceType)> for Balance {
230    fn from(value: (T, BalanceType)) -> Self {
231        Self(value.0.into(), value.1)
232    }
233}
234
235impl PartialOrd for Balance {
236    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
237        self.1.eq(&other.1).then(|| self.0.partial_cmp(&other.0)).flatten()
238    }
239}
240
241impl<T: Into<U256>> Add<T> for Balance {
242    type Output = Balance;
243
244    fn add(self, rhs: T) -> Self::Output {
245        Self(self.0.saturating_add(rhs.into()), self.1)
246    }
247}
248
249impl Add for Balance {
250    type Output = Balance;
251
252    fn add(self, rhs: Self) -> Self::Output {
253        self.add(rhs.0)
254    }
255}
256
257impl Add<&Balance> for Balance {
258    type Output = Balance;
259
260    fn add(self, rhs: &Balance) -> Self::Output {
261        self.add(rhs.0)
262    }
263}
264
265impl<T: Into<U256>> Sub<T> for Balance {
266    type Output = Balance;
267
268    fn sub(self, rhs: T) -> Self::Output {
269        Self(self.0.saturating_sub(rhs.into()), self.1)
270    }
271}
272
273impl Sub for Balance {
274    type Output = Balance;
275
276    fn sub(self, rhs: Self) -> Self::Output {
277        self.sub(rhs.0)
278    }
279}
280
281impl Sub<&Balance> for Balance {
282    type Output = Balance;
283
284    fn sub(self, rhs: &Balance) -> Self::Output {
285        self.sub(rhs.0)
286    }
287}
288
289impl<T: Into<U256>> Mul<T> for Balance {
290    type Output = Balance;
291
292    fn mul(self, rhs: T) -> Self::Output {
293        Self(self.0.saturating_mul(rhs.into()), self.1)
294    }
295}
296
297impl Mul for Balance {
298    type Output = Balance;
299
300    fn mul(self, rhs: Self) -> Self::Output {
301        self.mul(rhs.0)
302    }
303}
304
305impl UnitaryFloatOps for Balance {
306    fn mul_f64(&self, rhs: f64) -> Result<Self> {
307        self.0.mul_f64(rhs).map(|v| Self(v, self.1))
308    }
309
310    fn div_f64(&self, rhs: f64) -> Result<Self> {
311        self.0.div_f64(rhs).map(|v| Self(v, self.1))
312    }
313}
314
315impl Debug for Balance {
316    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
317        // Intentionally same as Display
318        write!(f, "{} {}", self.0, self.1)
319    }
320}
321
322impl Display for Balance {
323    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
324        write!(f, "{} {}", self.0, self.1)
325    }
326}
327
328impl FromStr for Balance {
329    type Err = GeneralError;
330
331    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
332        let regex = Regex::new(r"^\s*(\d+)\s*([A-z]+)\s*$").expect("should use valid regex pattern");
333        let cap = regex.captures(s).ok_or(ParseError("Balance".into()))?;
334
335        if cap.len() == 3 {
336            Ok(Self::new_from_str(
337                &cap[1],
338                BalanceType::from_str(&cap[2]).map_err(|_| ParseError("Balance".into()))?,
339            ))
340        } else {
341            Err(ParseError("Balance".into()))
342        }
343    }
344}
345
346/// Represents and Ethereum challenge.
347/// This is a one-way encoding of the secp256k1 curve point to an Ethereum address.
348#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
349pub struct EthereumChallenge([u8; Self::SIZE]);
350
351impl EthereumChallenge {
352    pub fn new(data: &[u8]) -> Self {
353        assert_eq!(data.len(), Self::SIZE);
354
355        let mut ret = Self::default();
356        ret.0.copy_from_slice(data);
357        ret
358    }
359}
360
361impl AsRef<[u8]> for EthereumChallenge {
362    fn as_ref(&self) -> &[u8] {
363        &self.0
364    }
365}
366
367impl TryFrom<&[u8]> for EthereumChallenge {
368    type Error = GeneralError;
369
370    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
371        Ok(Self(
372            value.try_into().map_err(|_| ParseError("EthereumChallenge".into()))?,
373        ))
374    }
375}
376
377impl BytesRepresentable for EthereumChallenge {
378    const SIZE: usize = 20;
379}
380
381impl IntoEndian<32> for U256 {
382    fn from_be_bytes<T: AsRef<[u8]>>(bytes: T) -> Self {
383        U256::from_big_endian(bytes.as_ref())
384    }
385
386    fn from_le_bytes<T: AsRef<[u8]>>(bytes: T) -> Self {
387        U256::from_little_endian(bytes.as_ref())
388    }
389
390    fn to_le_bytes(self) -> [u8; 32] {
391        let mut ret = [0u8; 32];
392        self.to_little_endian(&mut ret);
393        ret
394    }
395
396    fn to_be_bytes(self) -> [u8; 32] {
397        let mut ret = [0u8; 32];
398        self.to_big_endian(&mut ret);
399        ret
400    }
401}
402
403impl UnitaryFloatOps for U256 {
404    fn mul_f64(&self, rhs: f64) -> Result<Self> {
405        if !(0.0..=1.0).contains(&rhs) {
406            return Err(InvalidInput);
407        }
408
409        if rhs == 1.0 {
410            // special case: mantissa extraction does not work here
411            Ok(Self(self.0))
412        } else if rhs == 0.0 {
413            // special case: prevent from potential underflow errors
414            Ok(U256::zero())
415        } else {
416            Ok(
417                (*self * U256::from((rhs + 1.0 + f64::EPSILON).to_bits() & 0x000fffffffffffff_u64))
418                    >> U256::from(52_u64),
419            )
420        }
421    }
422
423    fn div_f64(&self, rhs: f64) -> Result<Self> {
424        if rhs <= 0.0 || rhs > 1.0 {
425            return Err(InvalidInput);
426        }
427
428        if rhs == 1.0 {
429            Ok(Self(self.0))
430        } else {
431            let nom = *self << U256::from(52_u64);
432            let denom = U256::from((rhs + 1.0).to_bits() & 0x000fffffffffffff_u64);
433
434            Ok(nom / denom)
435        }
436    }
437}
438
439/// A type containing selected fields from  the `eth_getLogs` RPC calls.
440///
441/// This is further restricted to already mined blocks.
442#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
443pub struct SerializableLog {
444    /// Contract address
445    pub address: Address,
446    /// Topics
447    pub topics: Vec<[u8; 32]>,
448    /// Raw log data
449    pub data: Vec<u8>,
450    /// Transaction index
451    pub tx_index: u64,
452    /// Corresponding block number
453    pub block_number: u64,
454    /// Corresponding block hash
455    pub block_hash: [u8; 32],
456    /// Corresponding transaction hash
457    pub tx_hash: [u8; 32],
458    /// Log index
459    pub log_index: u64,
460    /// Removed flag
461    pub removed: bool,
462    /// Processed flag
463    pub processed: Option<bool>,
464    /// Processed time
465    #[serde(with = "ts_seconds_option")]
466    pub processed_at: Option<DateTime<Utc>>,
467    /// Log hashes checksum
468    pub checksum: Option<String>,
469}
470
471impl Display for SerializableLog {
472    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
473        write!(
474            f,
475            "log #{} in tx #{} in block #{} of address {} with {} topics",
476            self.log_index,
477            self.tx_index,
478            self.block_number,
479            self.address,
480            self.topics.len()
481        )
482    }
483}
484
485impl Ord for SerializableLog {
486    fn cmp(&self, other: &Self) -> Ordering {
487        let block_number_order = self.block_number.cmp(&other.block_number);
488        if block_number_order == Ordering::Equal {
489            let tx_index_order = self.tx_index.cmp(&other.tx_index);
490            if tx_index_order == Ordering::Equal {
491                self.log_index.cmp(&other.log_index)
492            } else {
493                tx_index_order
494            }
495        } else {
496            block_number_order
497        }
498    }
499}
500
501impl PartialOrd<Self> for SerializableLog {
502    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
503        Some(self.cmp(other))
504    }
505}
506
507/// Unit tests of pure Rust code
508#[cfg(test)]
509mod tests {
510    use super::*;
511    use hex_literal::hex;
512    use primitive_types::U256;
513    use std::cmp::Ordering;
514    use std::str::FromStr;
515
516    #[test]
517    fn address_tests() -> anyhow::Result<()> {
518        let addr_1 = Address::try_from(hex!("Cf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"))?;
519        let addr_2 = Address::try_from(addr_1.as_ref())?;
520
521        assert_eq!(addr_1, addr_2, "deserialized address does not match");
522        assert_eq!(addr_1, Address::from_str("Cf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9")?);
523
524        assert_eq!(addr_1, Address::from_str("0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9")?);
525
526        assert_eq!(addr_1, Address::from_str(&addr_1.to_hex())?);
527
528        Ok(())
529    }
530
531    #[test]
532    fn balance_test_arithmetic() {
533        let test_1 = 100_u32;
534        let test_2 = 200_u32;
535
536        let b3 = Balance::new(test_1, BalanceType::HOPR);
537        let b4 = Balance::new(test_2, BalanceType::HOPR);
538
539        assert_eq!(test_1 + test_2, b3.add(b4).amount().as_u32(), "add test failed");
540        assert_eq!(test_2 - test_1, b4.sub(b3).amount().as_u32(), "sub test failed");
541        assert_eq!(test_2 - 10, b4.sub(10).amount().as_u32(), "sub test failed");
542
543        assert_eq!(0_u32, b3.sub(b4).amount().as_u32(), "negative test failed");
544        assert_eq!(0_u32, b3.sub(test_2 as u64).amount().as_u32(), "negative test failed");
545
546        assert_eq!(
547            test_1 * test_2,
548            b3.mul(b4).amount().as_u32(),
549            "multiplication test failed"
550        );
551        assert_eq!(
552            test_2 * test_1,
553            b4.mul(b3).amount().as_u32(),
554            "multiplication test failed"
555        );
556        assert_eq!(
557            test_2 * test_1,
558            b4.mul(test_1 as u64).amount().as_u32(),
559            "multiplication test failed"
560        );
561
562        assert!(matches!(b3.partial_cmp(&b4), Some(Ordering::Less)));
563        assert!(matches!(b4.partial_cmp(&b3), Some(Ordering::Greater)));
564
565        assert!(matches!(b3.partial_cmp(&b3), Some(Ordering::Equal)));
566        assert!(matches!(b4.partial_cmp(&b4), Some(Ordering::Equal)));
567
568        let other: Balance = (b4.amount(), BalanceType::Native).into();
569        assert!(other.partial_cmp(&b4).is_none());
570    }
571
572    #[test]
573    fn balance_test_formatted_string() {
574        let mut base = "123".to_string();
575        for _ in 0..Balance::SCALE - 3 {
576            base += "0";
577        }
578
579        let b1 = Balance::new_from_str(&base, BalanceType::HOPR);
580        let b2 = b1.mul(100);
581        let b3 = Balance::new_from_str(&base[..Balance::SCALE - 3], BalanceType::HOPR);
582        let b4 = Balance::new_from_str(&base[..Balance::SCALE - 1], BalanceType::HOPR);
583
584        assert_eq!("1.230000000000000000 HOPR", b1.to_formatted_string());
585        assert_eq!("123.0000000000000000 HOPR", b2.to_formatted_string());
586        assert_eq!("0.001230000000000000 HOPR", b3.to_formatted_string());
587        assert_eq!("0.123000000000000000 HOPR", b4.to_formatted_string());
588    }
589
590    #[test]
591    fn balance_test_value_string() {
592        let mut base = "123".to_string();
593        for _ in 0..Balance::SCALE - 3 {
594            base += "0";
595        }
596
597        let b1 = Balance::new_from_str(&base, BalanceType::HOPR);
598        let b2 = b1.mul(100);
599        let b3 = Balance::new_from_str(&base[..Balance::SCALE - 3], BalanceType::HOPR);
600        let b4 = Balance::new_from_str(&base[..Balance::SCALE - 1], BalanceType::HOPR);
601
602        assert_eq!("1230000000000000000", b1.to_value_string());
603        assert_eq!("123000000000000000000", b2.to_value_string());
604        assert_eq!("1230000000000000", b3.to_value_string());
605        assert_eq!("123000000000000000", b4.to_value_string());
606    }
607
608    #[test]
609    fn eth_challenge_tests() -> anyhow::Result<()> {
610        let e_1 = EthereumChallenge::default();
611        let e_2 = EthereumChallenge::try_from(e_1.as_ref())?;
612
613        assert_eq!(e_1, e_2);
614
615        Ok(())
616    }
617
618    #[test]
619    fn u256_float_multiply() -> anyhow::Result<()> {
620        assert_eq!(U256::one(), U256::one().mul_f64(1.0f64)?);
621        assert_eq!(U256::one(), U256::from(10u64).mul_f64(0.1f64)?);
622
623        // bad examples
624        assert!(U256::one().mul_f64(-1.0).is_err());
625        assert!(U256::one().mul_f64(1.1).is_err());
626
627        Ok(())
628    }
629
630    #[test]
631    fn u256_float_divide() -> anyhow::Result<()> {
632        assert_eq!(U256::one(), U256::one().div_f64(1.0f64)?);
633
634        assert_eq!(U256::from(2u64), U256::one().div_f64(0.5f64)?);
635        assert_eq!(U256::from(10000u64), U256::one().div_f64(0.0001f64)?);
636
637        // bad examples
638        assert!(U256::one().div_f64(0.0).is_err());
639        assert!(U256::one().div_f64(1.1).is_err());
640
641        Ok(())
642    }
643
644    #[test]
645    fn u256_endianness() {
646        let num: U256 = 123456789000_u128.into();
647
648        let be_bytes = num.to_be_bytes();
649        let le_bytes = num.to_le_bytes();
650
651        assert_ne!(
652            be_bytes, le_bytes,
653            "sanity check: input number must have different endianness"
654        );
655
656        let expected_be = hex!("0000000000000000000000000000000000000000000000000000001CBE991A08");
657        assert_eq!(expected_be, be_bytes);
658        assert_eq!(U256::from_be_bytes(expected_be), num);
659
660        let expected_le = hex!("081A99BE1C000000000000000000000000000000000000000000000000000000");
661        assert_eq!(expected_le, le_bytes);
662        assert_eq!(U256::from_le_bytes(expected_le), num);
663    }
664
665    #[test]
666    fn address_to_checksum_all_caps() -> anyhow::Result<()> {
667        let addr_1 = Address::from_str("52908400098527886e0f7030069857d2e4169ee7")?;
668        let value_1 = addr_1.to_checksum();
669        let addr_2 = Address::from_str("8617e340b3d01fa5f11f306f4090fd50e238070d")?;
670        let value_2 = addr_2.to_checksum();
671
672        assert_eq!(
673            value_1, "0x52908400098527886E0F7030069857D2E4169EE7",
674            "checksumed address does not match"
675        );
676        assert_eq!(
677            value_2, "0x8617E340B3D01FA5F11F306F4090FD50E238070D",
678            "checksumed address does not match"
679        );
680
681        Ok(())
682    }
683
684    #[test]
685    fn address_to_checksum_all_lower() -> anyhow::Result<()> {
686        let addr_1 = Address::from_str("de709f2102306220921060314715629080e2fb77")?;
687        let value_1 = addr_1.to_checksum();
688        let addr_2 = Address::from_str("27b1fdb04752bbc536007a920d24acb045561c26")?;
689        let value_2 = addr_2.to_checksum();
690
691        assert_eq!(
692            value_1, "0xde709f2102306220921060314715629080e2fb77",
693            "checksumed address does not match"
694        );
695        assert_eq!(
696            value_2, "0x27b1fdb04752bbc536007a920d24acb045561c26",
697            "checksumed address does not match"
698        );
699
700        Ok(())
701    }
702
703    #[test]
704    fn address_to_checksum_all_normal() -> anyhow::Result<()> {
705        let addr_1 = Address::from_str("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")?;
706        let addr_2 = Address::from_str("fb6916095ca1df60bb79ce92ce3ea74c37c5d359")?;
707        let addr_3 = Address::from_str("dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb")?;
708        let addr_4 = Address::from_str("d1220a0cf47c7b9be7a2e6ba89f429762e7b9adb")?;
709
710        let value_1 = addr_1.to_checksum();
711        let value_2 = addr_2.to_checksum();
712        let value_3 = addr_3.to_checksum();
713        let value_4 = addr_4.to_checksum();
714
715        assert_eq!(
716            value_1, "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
717            "checksumed address does not match"
718        );
719        assert_eq!(
720            value_2, "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
721            "checksumed address does not match"
722        );
723        assert_eq!(
724            value_3, "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
725            "checksumed address does not match"
726        );
727        assert_eq!(
728            value_4, "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
729            "checksumed address does not match"
730        );
731
732        Ok(())
733    }
734}