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#[derive(Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize, Hash, PartialOrd, Ord)]
19pub struct Address([u8; Self::SIZE]);
20
21impl Debug for Address {
22 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 pub fn is_zero(&self) -> bool {
51 self.0.iter().all(|e| 0_u8.eq(e))
52 }
53
54 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 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#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString)]
129pub enum BalanceType {
130 Native,
132 HOPR,
134}
135
136impl BalanceType {
137 pub fn one(self) -> Balance {
139 self.balance(1)
140 }
141
142 pub fn zero(self) -> Balance {
144 self.balance(0)
145 }
146
147 pub fn balance<T: Into<U256>>(self, amount: T) -> Balance {
149 Balance::new(amount, self)
150 }
151
152 pub fn balance_bytes<T: AsRef<[u8]>>(self, bytes: T) -> Balance {
156 Balance::new(U256::from_be_bytes(bytes), self)
157 }
158}
159
160#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162pub struct Balance(U256, BalanceType);
163
164impl Balance {
165 pub const SCALE: usize = 19;
167
168 pub fn new<T: Into<U256>>(value: T, balance_type: BalanceType) -> Self {
170 Self(value.into(), balance_type)
171 }
172
173 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 pub fn zero(balance_type: BalanceType) -> Self {
183 Self(U256::zero(), balance_type)
184 }
185
186 pub fn balance_type(&self) -> BalanceType {
188 self.1
189 }
190
191 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 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#[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 Ok(Self(self.0))
412 } else if rhs == 0.0 {
413 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#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
443pub struct SerializableLog {
444 pub address: Address,
446 pub topics: Vec<[u8; 32]>,
448 pub data: Vec<u8>,
450 pub tx_index: u64,
452 pub block_number: u64,
454 pub block_hash: [u8; 32],
456 pub tx_hash: [u8; 32],
458 pub log_index: u64,
460 pub removed: bool,
462 pub processed: Option<bool>,
464 #[serde(with = "ts_seconds_option")]
466 pub processed_at: Option<DateTime<Utc>>,
467 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#[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 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 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}