Skip to main content

erc20_payment_lib_common/
utils.rs

1use chrono::{DateTime, Utc};
2use rust_decimal::prelude::ToPrimitive;
3use rust_decimal::Decimal;
4use std::env;
5use std::error::Error;
6use std::fmt::{Display, Formatter};
7use web3::types::U256;
8
9pub fn datetime_from_u256_timestamp(timestamp: U256) -> Option<DateTime<Utc>> {
10    DateTime::from_timestamp(timestamp.as_u64() as i64, 0)
11}
12
13pub fn datetime_from_u256_with_option(timestamp: U256) -> Option<DateTime<Utc>> {
14    if timestamp.is_zero() {
15        None
16    } else {
17        datetime_from_u256_timestamp(timestamp)
18    }
19}
20
21pub fn get_env_bool_value(env_name: &str) -> bool {
22    env::var(env_name)
23        .map(|v| {
24            if v == "1" || v == "true" {
25                true
26            } else {
27                if v != "0" && v != "false" {
28                    log::warn!("Invalid value for {}: {} assuming false", env_name, v);
29                }
30                false
31            }
32        })
33        .unwrap_or(false)
34}
35
36#[derive(Debug, Clone)]
37pub struct ConversionError {
38    pub msg: String,
39}
40
41impl ConversionError {
42    pub fn from(msg: String) -> Self {
43        Self { msg }
44    }
45}
46
47impl Display for ConversionError {
48    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49        write!(f, "Error during conversion: {}", self.msg)
50    }
51}
52
53impl Error for ConversionError {
54    fn description(&self) -> &str {
55        "Conversion error"
56    }
57}
58
59fn compute_base(num_decimals: u32) -> rust_decimal::Decimal {
60    if num_decimals == 18 {
61        Decimal::new(1000000000000000000, 0)
62    } else if num_decimals == 6 {
63        Decimal::new(1000000, 0)
64    } else {
65        Decimal::from(10_u128.pow(num_decimals))
66    }
67}
68
69///good from one gwei up to at least one billion ethers
70fn rust_dec_to_u256_strict(
71    dec_amount: rust_decimal::Decimal,
72    decimals: Option<u32>,
73) -> Result<U256, ConversionError> {
74    let num_decimals = decimals.unwrap_or(18);
75    if num_decimals > 18 {
76        return Err(ConversionError {
77            msg: format!("Decimals: {num_decimals} cannot be greater than 18"),
78        });
79    }
80
81    let dec_base = compute_base(num_decimals);
82    //println!("dec: {}, number scale: {}", dec_base, dec_base.scale());
83
84    let dec_mul = dec_amount.checked_mul(dec_base).ok_or(ConversionError {
85        msg: "Overflow during conversion".to_string(),
86    })?;
87    //println!("number: {}, number scale: {}", dec_mul, dec_mul.scale());
88
89    let dec_mul = dec_mul.normalize();
90    //println!("number normalized: {}", dec_mul);
91
92    if dec_mul.fract() != Decimal::from(0) {
93        return Err(ConversionError::from(format!(
94            "Number cannot have a fractional part {dec_mul}"
95        )));
96    }
97    let u128 = dec_mul.to_u128().ok_or_else(|| {
98        ConversionError::from(format!("Number cannot be converted to u128 {dec_mul}"))
99    })?;
100    Ok(U256::from(u128))
101}
102
103#[derive(Debug, Clone, Copy)]
104pub enum Decimals {
105    Zero = 0,
106    Six = 6,
107    Nine = 9,
108    Eighteen = 18,
109}
110
111#[allow(dead_code)]
112fn rust_dec_to_u256(dec_amount: rust_decimal::Decimal, decimals: Decimals) -> U256 {
113    if dec_amount < Decimal::from(0) {
114        return U256::zero();
115    }
116    let num_decimals = match decimals {
117        Decimals::Zero => 0,
118        Decimals::Six => 6,
119        Decimals::Nine => 9,
120        Decimals::Eighteen => 18,
121    };
122
123    let dec_base = compute_base(num_decimals);
124    //println!("dec: {}, number scale: {}", dec_base, dec_base.scale());
125
126    let dec_mul = match dec_amount.checked_mul(dec_base) {
127        Some(dec_mul) => dec_mul,
128        None => {
129            log::warn!(
130                "Overflow during multiplication dec_amount: {} dec_base: {}. Using saturated mul",
131                dec_amount,
132                dec_base
133            );
134            dec_amount.saturating_mul(dec_base)
135        }
136    };
137
138    //println!("number: {}, number scale: {}", dec_mul, dec_mul.scale());
139
140    let dec_mul = dec_mul.normalize();
141    //println!("number normalized: {}", dec_mul);
142
143    if dec_mul.fract() != Decimal::from(0) {
144        log::warn!("Number have a fractional part which will be truncated {dec_mul}");
145    }
146    let u128 = dec_mul.to_u128().unwrap_or(
147        // to_u128 is failing only if dec_mul is negative which is not possible here
148        0,
149    );
150
151    U256::from(u128)
152}
153
154fn u256_to_rust_dec(
155    amount: U256,
156    decimals: Option<u32>,
157) -> Result<rust_decimal::Decimal, ConversionError> {
158    let num_decimals = decimals.unwrap_or(18);
159    if num_decimals > 18 {
160        return Err(ConversionError {
161            msg: format!("Decimals: {num_decimals} cannot be greater than 18"),
162        });
163    }
164
165    let dec_base = compute_base(num_decimals);
166
167    //max value supported by rust_decimal
168    if amount >= U256::from(79228162514264337593543950336_u128) {
169        return Err(ConversionError {
170            msg: format!(
171                "Amount greater than max rust_decimal: {amount}>=79228162514264337593543950336"
172            ),
173        });
174    }
175
176    Ok(Decimal::from(amount.as_u128()) / dec_base)
177}
178
179fn u256_to_gwei(amount: U256) -> Result<Decimal, ConversionError> {
180    u256_to_rust_dec(amount, Some(9))
181}
182
183pub trait U256ConvExt {
184    fn to_gwei(&self) -> Result<Decimal, ConversionError>;
185    fn to_gwei_saturate(&self) -> Decimal;
186    fn to_eth(&self) -> Result<Decimal, ConversionError>;
187    fn to_eth_saturate(&self) -> Decimal;
188    fn to_gwei_str(&self) -> String;
189    fn to_eth_str(&self) -> String;
190    fn to_gwei_str_with_precision(&self, precision: u8) -> String;
191    fn to_eth_str_with_precision(&self, precision: u8) -> String;
192}
193
194impl U256ConvExt for U256 {
195    fn to_gwei(&self) -> Result<Decimal, ConversionError> {
196        u256_to_gwei(*self)
197    }
198    fn to_gwei_saturate(&self) -> Decimal {
199        u256_to_gwei(*self).unwrap_or(Decimal::from(10000000000000_u64))
200    }
201    fn to_eth(&self) -> Result<Decimal, ConversionError> {
202        u256_to_eth(*self)
203    }
204    fn to_eth_saturate(&self) -> Decimal {
205        u256_to_eth(*self).unwrap_or(Decimal::from(10000000000_u64))
206    }
207    fn to_gwei_str(&self) -> String {
208        u256_to_decimal_string(*self, Decimals::Nine, None)
209    }
210    fn to_eth_str(&self) -> String {
211        u256_to_decimal_string(*self, Decimals::Eighteen, None)
212    }
213    fn to_gwei_str_with_precision(&self, precision: u8) -> String {
214        u256_to_decimal_string(*self, Decimals::Nine, Some(precision as usize))
215    }
216    fn to_eth_str_with_precision(&self, precision: u8) -> String {
217        u256_to_decimal_string(*self, Decimals::Eighteen, Some(precision as usize))
218    }
219}
220
221pub trait StringConvExt {
222    fn to_gwei(&self) -> Result<Decimal, ConversionError>;
223    fn to_eth(&self) -> Result<Decimal, ConversionError>;
224    fn to_u256(&self) -> Result<U256, ConversionError>;
225}
226impl StringConvExt for String {
227    fn to_gwei(&self) -> Result<Decimal, ConversionError> {
228        self.to_u256()?.to_gwei()
229    }
230    fn to_eth(&self) -> Result<Decimal, ConversionError> {
231        self.to_u256()?.to_eth()
232    }
233
234    fn to_u256(&self) -> Result<U256, ConversionError> {
235        U256::from_dec_str(self).map_err(|err| {
236            ConversionError::from(format!("Invalid string when converting: {err:?}"))
237        })
238    }
239}
240
241pub trait DecimalConvExt {
242    fn to_u256_from_gwei(&self) -> Result<U256, ConversionError>;
243    fn to_u256_from_eth(&self) -> Result<U256, ConversionError>;
244}
245
246impl DecimalConvExt for Decimal {
247    fn to_u256_from_gwei(&self) -> Result<U256, ConversionError> {
248        rust_dec_to_u256_strict(*self, Some(9))
249    }
250    fn to_u256_from_eth(&self) -> Result<U256, ConversionError> {
251        rust_dec_to_u256_strict(*self, Some(18))
252    }
253}
254
255fn u256_to_eth(amount: U256) -> Result<Decimal, ConversionError> {
256    u256_to_rust_dec(amount, Some(18))
257}
258
259pub fn u256_eth_from_str(val: &str) -> Result<(U256, Decimal), ConversionError> {
260    let u256 = U256::from_dec_str(val)
261        .map_err(|err| ConversionError::from(format!("Invalid string when converting: {err:?}")))?;
262    let eth = u256_to_eth(u256)?;
263    Ok((u256, eth))
264}
265
266pub fn u256_gwei_from_str(val: &str) -> Result<(U256, Decimal), ConversionError> {
267    let u256 = U256::from_dec_str(val)
268        .map_err(|err| ConversionError::from(format!("Invalid string when converting: {err:?}")))?;
269    let gwei = u256_to_gwei(u256)?;
270    Ok((u256, gwei))
271}
272
273/// precision cannot be greater than decimals (it is capped automatically)
274pub fn u256_to_decimal_string(
275    amount: U256,
276    decimals: Decimals,
277    precision: Option<usize>,
278) -> String {
279    let str = &amount.to_string();
280    let mut str_rev: Vec<char> = str.chars().rev().collect();
281    let precision = precision.map(|p| std::cmp::min(p, decimals as usize));
282
283    #[allow(clippy::same_item_push)]
284    for _ in 0..(decimals as usize) {
285        str_rev.push('0');
286    }
287
288    str_rev.insert(decimals as usize, '.');
289
290    let str: String = str_rev.iter().rev().collect();
291    let str = str.trim_matches('0').to_string();
292    let mut str = if str.starts_with('.') {
293        "0".to_string() + &str
294    } else {
295        str
296    };
297
298    let idx_of_dot = str.find('.').unwrap_or(str.len()) as i64;
299    let number_of_digit_at_right = str.len() as i64 - idx_of_dot - 1;
300
301    if let Some(precision) = precision {
302        let add_zeroes = precision as i64 - number_of_digit_at_right;
303        if add_zeroes > 0 {
304            for _ in 0..add_zeroes {
305                str.push('0');
306            }
307        } else {
308            for _ in 0..(-add_zeroes) {
309                str.pop();
310            }
311        }
312    }
313
314    str = str.trim_end_matches('.').to_string();
315
316    str
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use rand::Rng;
323    use std::str::FromStr;
324
325    #[test]
326    #[rustfmt::skip]
327    fn test_rust_u256_to_str() {
328        assert_eq!(u256_to_decimal_string(U256::from(0), Decimals::Zero, None), "0");
329        assert_eq!(u256_to_decimal_string(U256::from(0), Decimals::Six, None), "0");
330        assert_eq!(u256_to_decimal_string(U256::from(0), Decimals::Nine, None), "0");
331        assert_eq!(u256_to_decimal_string(U256::from(0), Decimals::Eighteen, None), "0");
332        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Zero, None), "1");
333        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Six, None), "0.000001");
334        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Nine, None), "0.000000001");
335        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Eighteen, None), "0.000000000000000001");
336        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Six, Some(0)), "0");
337        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Six, Some(3)), "0.000");
338        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Six, Some(6)), "0.000001");
339        assert_eq!(u256_to_decimal_string(U256::from(1), Decimals::Six, Some(9)), "0.000001");
340
341        let max_u256_str = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
342        assert_eq!(u256_to_decimal_string(U256::from_dec_str(max_u256_str).unwrap(), Decimals::Zero, Some(2)),
343                   "115792089237316195423570985008687907853269984665640564039457584007913129639935");
344        assert_eq!(u256_to_decimal_string(U256::from_dec_str(max_u256_str).unwrap(), Decimals::Eighteen, Some(2)),
345            "115792089237316195423570985008687907853269984665640564039457.58");
346
347        let mut rng = rand::thread_rng();
348        for _ in 0..1000 {
349            let rand: u64 = rng.gen();
350
351            let u256 = U256::from(rand) / U256::from(1000000_u64);
352            assert_eq!(u256.to_string(), u256_to_decimal_string(U256::from(rand), Decimals::Six, Some(0)));
353        }
354
355        assert_eq!(u256_to_decimal_string(U256::from(1000000000000000000_u128), Decimals::Eighteen, None), "1");
356        assert_eq!(u256_to_decimal_string(U256::from(1000000000000000000000000000000000000_u128), Decimals::Eighteen, None), "1000000000000000000");
357        assert_eq!(u256_to_decimal_string(U256::from(1000000000000000000000000000000000000_u128), Decimals::Eighteen, Some(5)), "1000000000000000000.00000");
358        assert_eq!(u256_to_decimal_string(U256::from(1000000000000000000660000000000000000_u128), Decimals::Eighteen, None), "1000000000000000000.66");
359        assert_eq!(u256_to_decimal_string(U256::from(1000000000000000000660000000000000778_u128), Decimals::Eighteen, None), "1000000000000000000.660000000000000778");
360        assert_eq!(u256_to_decimal_string(U256::from(1000000000000000000660000000000000778_u128), Decimals::Eighteen, Some(16)), "1000000000000000000.6600000000000007");
361    }
362
363    #[test]
364    fn test_rust_decimal_conversion() {
365        let dec_gwei = Decimal::new(1, 18);
366        let res = rust_dec_to_u256_strict(dec_gwei, None).unwrap();
367        assert_eq!(res, U256::from(1));
368
369        let res = rust_dec_to_u256_strict(dec_gwei / Decimal::from(2), None);
370        println!("res: {res:?}");
371        assert!(res.err().unwrap().msg.contains("fractional"));
372
373        let res = rust_dec_to_u256_strict(dec_gwei / Decimal::from(2), Some(19));
374        println!("res: {res:?}");
375        assert!(res.err().unwrap().msg.contains("greater than 18"));
376
377        let res = rust_dec_to_u256_strict(Decimal::from(8777666555_u64), None).unwrap();
378        println!("res: {res:?}");
379        assert_eq!(
380            res,
381            U256::from(8777666555_u64) * U256::from(1000000000000000000_u64)
382        );
383
384        let res = rust_dec_to_u256_strict(Decimal::from(8777666555_u64) + dec_gwei, None).unwrap();
385        println!("res: {res:?}");
386        assert_eq!(res, U256::from(8777666555000000000000000001_u128));
387
388        let res = rust_dec_to_u256_strict(Decimal::from(0), None).unwrap();
389        println!("res: {res:?}");
390        assert_eq!(res, U256::from(0));
391
392        let res = rust_dec_to_u256_strict(Decimal::from(1), Some(0)).unwrap();
393        println!("res: {res:?}");
394        assert_eq!(res, U256::from(1));
395
396        let res = rust_dec_to_u256_strict(Decimal::from(1), Some(6)).unwrap();
397        println!("res: {res:?}");
398        assert_eq!(res, U256::from(1000000));
399
400        let res = rust_dec_to_u256_strict(Decimal::from(1), Some(9)).unwrap();
401        println!("res: {res:?}");
402        assert_eq!(res, U256::from(1000000000));
403
404        let res =
405            rust_dec_to_u256_strict(Decimal::from_str("123456789.123456789").unwrap(), Some(18))
406                .unwrap();
407        println!("res: {res:?}");
408        assert_eq!(
409            res,
410            U256::from_dec_str("123456789123456789000000000").unwrap()
411        );
412
413        //this should result in overflow, because 79228162514264337593543950336 == 2**96
414        let res = rust_dec_to_u256_strict(
415            Decimal::from_str("79228162514.264337593543950336").unwrap(),
416            Some(18),
417        );
418        println!("res: {res:?}");
419        assert!(res.err().unwrap().msg.to_lowercase().contains("overflow"));
420
421        //this is the max value that can be represented by rust decimal
422        let res = rust_dec_to_u256_strict(
423            Decimal::from_str("79228162514.264337593543950335").unwrap(),
424            Some(18),
425        )
426        .unwrap();
427        println!("res: {res:?}");
428        assert_eq!(res, U256::from(79228162514264337593543950335_u128));
429
430        //this is the max value that can be represented by rust decimal
431        let res = rust_dec_to_u256_strict(
432            Decimal::from_str("79228162514264337593543950335").unwrap(),
433            Some(0),
434        )
435        .unwrap();
436        println!("res: {res:?}");
437        assert_eq!(res, U256::from(79228162514264337593543950335_u128));
438
439        //this is the max value that can be represented by rust decimal
440        let res = rust_dec_to_u256_strict(
441            Decimal::from_str("79228162514264337593543.950335").unwrap(),
442            Some(6),
443        )
444        .unwrap();
445        println!("res: {res:?}");
446        assert_eq!(res, U256::from(79228162514264337593543950335_u128));
447
448        //this is the max value that can be represented by rust decimal
449        let res = rust_dec_to_u256_strict(
450            Decimal::from_str("792281625142643.37593543950335").unwrap(),
451            Some(14),
452        )
453        .unwrap();
454        println!("res: {res:?}");
455        assert_eq!(res, U256::from(79228162514264337593543950335_u128));
456        //assert_eq!(res, U256::zero());
457
458        let res = rust_dec_to_u256(
459            Decimal::from_str("79228162514.264337593543950335").unwrap(),
460            Decimals::Eighteen,
461        );
462        assert_eq!(res, U256::from(79228162514264337593543950335_u128));
463
464        let res = rust_dec_to_u256(
465            Decimal::from_str("2514.26433759354395033559999").unwrap(),
466            Decimals::Eighteen,
467        );
468        assert_eq!(res, U256::from(2514264337593543950335_u128));
469    }
470}