hopr_primitive_types/
primitives.rs

1use std::{
2    cmp::Ordering,
3    fmt::{Debug, Display, Formatter},
4    str::FromStr,
5};
6
7use chrono::{DateTime, Utc, serde::ts_seconds_option};
8use serde::{Deserialize, Serialize};
9use sha3::Digest;
10
11use crate::{
12    errors::{
13        GeneralError,
14        GeneralError::{InvalidInput, ParseError},
15        Result,
16    },
17    prelude::BytesRepresentable,
18    traits::{IntoEndian, ToHex, UnitaryFloatOps},
19};
20
21pub type U256 = primitive_types::U256;
22
23/// Represents an Ethereum address
24#[derive(Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize, Hash, PartialOrd, Ord)]
25pub struct Address([u8; Self::SIZE]);
26
27impl Debug for Address {
28    // Intentionally same as Display
29    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30        write!(f, "{}", self.to_hex())
31    }
32}
33
34impl Display for Address {
35    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}", self.to_hex())
37    }
38}
39
40impl Address {
41    pub fn new(bytes: &[u8]) -> Self {
42        assert_eq!(bytes.len(), Self::SIZE, "invalid length");
43        let mut ret = Self::default();
44        ret.0.copy_from_slice(bytes);
45        ret
46    }
47
48    pub fn to_bytes32(&self) -> Box<[u8]> {
49        let mut ret = Vec::with_capacity(12 + Self::SIZE);
50        ret.extend_from_slice(&[0u8; 12]);
51        ret.extend_from_slice(&self.0);
52        ret.into_boxed_slice()
53    }
54
55    /// Checks if the address is all zeroes.
56    pub fn is_zero(&self) -> bool {
57        self.0.iter().all(|e| 0_u8.eq(e))
58    }
59
60    /// Turns the address into a checksum-ed address string
61    /// according to [EIP-55](https://eips.ethereum.org/EIPS/eip-55).
62    pub fn to_checksum(&self) -> String {
63        let address_hex = hex::encode(self.0);
64
65        let hash = sha3::Keccak256::digest(address_hex.as_bytes());
66
67        let mut ret = String::with_capacity(Self::SIZE * 2 + 2);
68        ret.push_str("0x");
69
70        for (i, c) in address_hex.chars().enumerate() {
71            let nibble = (hash[i / 2] >> (((i + 1) % 2) * 4)) & 0xf;
72            if nibble < 8 {
73                ret.push(c);
74            } else {
75                ret.push(c.to_ascii_uppercase());
76            }
77        }
78        ret
79    }
80}
81
82impl AsRef<[u8]> for Address {
83    fn as_ref(&self) -> &[u8] {
84        &self.0
85    }
86}
87
88impl TryFrom<&[u8]> for Address {
89    type Error = GeneralError;
90
91    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
92        Ok(Self(value.try_into().map_err(|_| ParseError("Address".into()))?))
93    }
94}
95
96impl BytesRepresentable for Address {
97    /// Fixed the size of the address when encoded as bytes (e.g., via `as_ref()`).
98    const SIZE: usize = 20;
99}
100
101impl From<[u8; Address::SIZE]> for Address {
102    fn from(value: [u8; Address::SIZE]) -> Self {
103        Self(value)
104    }
105}
106
107impl From<Address> for [u8; Address::SIZE] {
108    fn from(value: Address) -> Self {
109        value.0
110    }
111}
112
113impl From<primitive_types::H160> for Address {
114    fn from(value: primitive_types::H160) -> Self {
115        Self(value.0)
116    }
117}
118
119impl From<Address> for primitive_types::H160 {
120    fn from(value: Address) -> Self {
121        primitive_types::H160::from_slice(&value.0)
122    }
123}
124
125impl FromStr for Address {
126    type Err = GeneralError;
127
128    fn from_str(value: &str) -> Result<Address> {
129        Self::from_hex(value)
130    }
131}
132
133impl From<alloy::primitives::Address> for Address {
134    fn from(a: alloy::primitives::Address) -> Self {
135        Address::from(a.0.0)
136    }
137}
138impl From<Address> for alloy::primitives::Address {
139    fn from(a: Address) -> Self {
140        alloy::primitives::Address::from_slice(a.as_ref())
141    }
142}
143
144/// Represents and Ethereum challenge.
145///
146/// This is a one-way encoding of the secp256k1 curve point to an Ethereum address.
147#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
148pub struct EthereumChallenge(pub Address);
149impl AsRef<[u8]> for EthereumChallenge {
150    fn as_ref(&self) -> &[u8] {
151        self.0.as_ref()
152    }
153}
154
155impl TryFrom<&[u8]> for EthereumChallenge {
156    type Error = GeneralError;
157
158    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
159        Ok(Self(
160            value.try_into().map_err(|_| ParseError("EthereumChallenge".into()))?,
161        ))
162    }
163}
164
165impl BytesRepresentable for EthereumChallenge {
166    const SIZE: usize = Address::SIZE;
167}
168
169impl IntoEndian<32> for U256 {
170    fn from_be_bytes<T: AsRef<[u8]>>(bytes: T) -> Self {
171        U256::from_big_endian(bytes.as_ref())
172    }
173
174    fn from_le_bytes<T: AsRef<[u8]>>(bytes: T) -> Self {
175        U256::from_little_endian(bytes.as_ref())
176    }
177
178    fn to_le_bytes(self) -> [u8; 32] {
179        self.to_little_endian()
180    }
181
182    fn to_be_bytes(self) -> [u8; 32] {
183        self.to_big_endian()
184    }
185}
186
187impl UnitaryFloatOps for U256 {
188    fn mul_f64(&self, rhs: f64) -> Result<Self> {
189        if !(0.0..=1.0).contains(&rhs) {
190            return Err(InvalidInput);
191        }
192
193        if rhs == 1.0 {
194            // special case: mantissa extraction does not work here
195            Ok(Self(self.0))
196        } else if rhs == 0.0 {
197            // special case: prevent from potential underflow errors
198            Ok(U256::zero())
199        } else {
200            Ok(
201                (*self * U256::from((rhs + 1.0 + f64::EPSILON).to_bits() & 0x000fffffffffffff_u64))
202                    >> U256::from(52_u64),
203            )
204        }
205    }
206
207    fn div_f64(&self, rhs: f64) -> Result<Self> {
208        if rhs <= 0.0 || rhs > 1.0 {
209            return Err(InvalidInput);
210        }
211
212        if rhs == 1.0 {
213            Ok(Self(self.0))
214        } else {
215            let nom = *self << U256::from(52_u64);
216            let denom = U256::from((rhs + 1.0).to_bits() & 0x000fffffffffffff_u64);
217
218            Ok(nom / denom)
219        }
220    }
221}
222
223/// A type containing selected fields from  the `eth_getLogs` RPC calls.
224///
225/// This is further restricted to already mined blocks.
226#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
227pub struct SerializableLog {
228    /// Contract address
229    pub address: Address,
230    /// Topics
231    pub topics: Vec<[u8; 32]>,
232    /// Raw log data
233    pub data: Vec<u8>,
234    /// Transaction index
235    pub tx_index: u64,
236    /// Corresponding block number
237    pub block_number: u64,
238    /// Corresponding block hash
239    pub block_hash: [u8; 32],
240    /// Corresponding transaction hash
241    pub tx_hash: [u8; 32],
242    /// Log index
243    pub log_index: u64,
244    /// Removed flag
245    pub removed: bool,
246    /// Processed flag
247    pub processed: Option<bool>,
248    /// Processed time
249    #[serde(with = "ts_seconds_option")]
250    pub processed_at: Option<DateTime<Utc>>,
251    /// Log hashes checksum
252    pub checksum: Option<String>,
253}
254
255impl Display for SerializableLog {
256    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
257        write!(
258            f,
259            "log #{} in tx #{} in block #{} of address {} with {} topics",
260            self.log_index,
261            self.tx_index,
262            self.block_number,
263            self.address,
264            self.topics.len()
265        )
266    }
267}
268
269impl Ord for SerializableLog {
270    fn cmp(&self, other: &Self) -> Ordering {
271        let block_number_order = self.block_number.cmp(&other.block_number);
272        if block_number_order == Ordering::Equal {
273            let tx_index_order = self.tx_index.cmp(&other.tx_index);
274            if tx_index_order == Ordering::Equal {
275                self.log_index.cmp(&other.log_index)
276            } else {
277                tx_index_order
278            }
279        } else {
280            block_number_order
281        }
282    }
283}
284
285impl PartialOrd<Self> for SerializableLog {
286    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
287        Some(self.cmp(other))
288    }
289}
290
291/// Identifier of public keys.
292#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
293pub struct KeyIdent<const N: usize = 4>(#[serde(with = "serde_bytes")] [u8; N]);
294
295impl<const N: usize> Display for KeyIdent<N> {
296    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
297        write!(f, "{}", self.to_hex())
298    }
299}
300
301impl From<u32> for KeyIdent<4> {
302    fn from(value: u32) -> Self {
303        Self(value.to_be_bytes())
304    }
305}
306
307impl From<KeyIdent<4>> for u32 {
308    fn from(value: KeyIdent<4>) -> Self {
309        u32::from_be_bytes(value.0)
310    }
311}
312
313impl From<u64> for KeyIdent<8> {
314    fn from(value: u64) -> Self {
315        Self(value.to_be_bytes())
316    }
317}
318
319impl From<KeyIdent<8>> for u64 {
320    fn from(value: KeyIdent<8>) -> Self {
321        u64::from_be_bytes(value.0)
322    }
323}
324
325impl<const N: usize> TryFrom<&[u8]> for KeyIdent<N> {
326    type Error = GeneralError;
327
328    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
329        Ok(Self(value.try_into().map_err(|_| ParseError("KeyIdent".into()))?))
330    }
331}
332
333impl<const N: usize> AsRef<[u8]> for KeyIdent<N> {
334    fn as_ref(&self) -> &[u8] {
335        &self.0
336    }
337}
338
339impl<const N: usize> Default for KeyIdent<N> {
340    fn default() -> Self {
341        Self([0u8; N])
342    }
343}
344
345impl<const N: usize> BytesRepresentable for KeyIdent<N> {
346    const SIZE: usize = N;
347}
348
349/// Unit tests of pure Rust code
350#[cfg(test)]
351mod tests {
352    use std::str::FromStr;
353
354    use hex_literal::hex;
355    use primitive_types::U256;
356
357    use super::*;
358
359    #[test]
360    fn address_tests() -> anyhow::Result<()> {
361        let addr_1 = Address::from(hex!("Cf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"));
362        let addr_2 = Address::try_from(addr_1.as_ref())?;
363
364        assert_eq!(addr_1, addr_2, "deserialized address does not match");
365        assert_eq!(addr_1, Address::from_str("Cf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9")?);
366
367        assert_eq!(addr_1, Address::from_str("0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9")?);
368
369        assert_eq!(addr_1, Address::from_str(&addr_1.to_hex())?);
370
371        Ok(())
372    }
373
374    #[test]
375    fn eth_challenge_tests() -> anyhow::Result<()> {
376        let e_1 = EthereumChallenge::default();
377        let e_2 = EthereumChallenge::try_from(e_1.as_ref())?;
378
379        assert_eq!(e_1, e_2);
380
381        Ok(())
382    }
383
384    #[test]
385    fn u256_float_multiply() -> anyhow::Result<()> {
386        assert_eq!(U256::one(), U256::one().mul_f64(1.0f64)?);
387        assert_eq!(U256::one(), U256::from(10u64).mul_f64(0.1f64)?);
388
389        // bad examples
390        assert!(U256::one().mul_f64(-1.0).is_err());
391        assert!(U256::one().mul_f64(1.1).is_err());
392
393        Ok(())
394    }
395
396    #[test]
397    fn u256_float_divide() -> anyhow::Result<()> {
398        assert_eq!(U256::one(), U256::one().div_f64(1.0f64)?);
399
400        assert_eq!(U256::from(2u64), U256::one().div_f64(0.5f64)?);
401        assert_eq!(U256::from(10000u64), U256::one().div_f64(0.0001f64)?);
402
403        // bad examples
404        assert!(U256::one().div_f64(0.0).is_err());
405        assert!(U256::one().div_f64(1.1).is_err());
406
407        Ok(())
408    }
409
410    #[test]
411    fn u256_endianness() {
412        let num: U256 = 123456789000_u128.into();
413
414        let be_bytes = num.to_be_bytes();
415        let le_bytes = num.to_le_bytes();
416
417        assert_ne!(
418            be_bytes, le_bytes,
419            "sanity check: input number must have different endianness"
420        );
421
422        let expected_be = hex!("0000000000000000000000000000000000000000000000000000001CBE991A08");
423        assert_eq!(expected_be, be_bytes);
424        assert_eq!(U256::from_be_bytes(expected_be), num);
425
426        let expected_le = hex!("081A99BE1C000000000000000000000000000000000000000000000000000000");
427        assert_eq!(expected_le, le_bytes);
428        assert_eq!(U256::from_le_bytes(expected_le), num);
429    }
430
431    #[test]
432    fn address_to_checksum_all_caps() -> anyhow::Result<()> {
433        let addr_1 = Address::from_str("52908400098527886e0f7030069857d2e4169ee7")?;
434        let value_1 = addr_1.to_checksum();
435        let addr_2 = Address::from_str("8617e340b3d01fa5f11f306f4090fd50e238070d")?;
436        let value_2 = addr_2.to_checksum();
437
438        assert_eq!(
439            value_1, "0x52908400098527886E0F7030069857D2E4169EE7",
440            "checksumed address does not match"
441        );
442        assert_eq!(
443            value_2, "0x8617E340B3D01FA5F11F306F4090FD50E238070D",
444            "checksumed address does not match"
445        );
446
447        Ok(())
448    }
449
450    #[test]
451    fn address_to_checksum_all_lower() -> anyhow::Result<()> {
452        let addr_1 = Address::from_str("de709f2102306220921060314715629080e2fb77")?;
453        let value_1 = addr_1.to_checksum();
454        let addr_2 = Address::from_str("27b1fdb04752bbc536007a920d24acb045561c26")?;
455        let value_2 = addr_2.to_checksum();
456
457        assert_eq!(
458            value_1, "0xde709f2102306220921060314715629080e2fb77",
459            "checksumed address does not match"
460        );
461        assert_eq!(
462            value_2, "0x27b1fdb04752bbc536007a920d24acb045561c26",
463            "checksumed address does not match"
464        );
465
466        Ok(())
467    }
468
469    #[test]
470    fn address_to_checksum_all_normal() -> anyhow::Result<()> {
471        let addr_1 = Address::from_str("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")?;
472        let addr_2 = Address::from_str("fb6916095ca1df60bb79ce92ce3ea74c37c5d359")?;
473        let addr_3 = Address::from_str("dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb")?;
474        let addr_4 = Address::from_str("d1220a0cf47c7b9be7a2e6ba89f429762e7b9adb")?;
475
476        let value_1 = addr_1.to_checksum();
477        let value_2 = addr_2.to_checksum();
478        let value_3 = addr_3.to_checksum();
479        let value_4 = addr_4.to_checksum();
480
481        assert_eq!(
482            value_1, "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
483            "checksumed address does not match"
484        );
485        assert_eq!(
486            value_2, "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
487            "checksumed address does not match"
488        );
489        assert_eq!(
490            value_3, "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
491            "checksumed address does not match"
492        );
493        assert_eq!(
494            value_4, "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
495            "checksumed address does not match"
496        );
497
498        Ok(())
499    }
500}