From 39e4582b179fb31b94bdf3fb1735b257aad22390 Mon Sep 17 00:00:00 2001 From: Shuhui Luo Date: Mon, 25 Dec 2023 03:52:58 -0800 Subject: [PATCH] restrict `decimals` to `u8` and add token tests --- src/entities/base_currency.rs | 2 +- src/entities/currency.rs | 2 +- src/entities/ether.rs | 7 +- src/entities/fractions/currency_amount.rs | 6 +- src/entities/fractions/fraction.rs | 8 +- src/entities/fractions/percent.rs | 4 +- src/entities/fractions/price.rs | 8 +- src/entities/token.rs | 119 ++++++++++++++++------ 8 files changed, 105 insertions(+), 51 deletions(-) diff --git a/src/entities/base_currency.rs b/src/entities/base_currency.rs index 20d17e9..98a833f 100644 --- a/src/entities/base_currency.rs +++ b/src/entities/base_currency.rs @@ -6,7 +6,7 @@ pub trait BaseCurrency: Clone { fn chain_id(&self) -> u32; /// The decimals used in representing currency amounts - fn decimals(&self) -> u32; + fn decimals(&self) -> u8; /// The symbol of the currency, i.e. a short textual non-unique identifier fn symbol(&self) -> Option; diff --git a/src/entities/currency.rs b/src/entities/currency.rs index c2c0919..7be4707 100644 --- a/src/entities/currency.rs +++ b/src/entities/currency.rs @@ -37,7 +37,7 @@ impl BaseCurrency for Currency { } } - fn decimals(&self) -> u32 { + fn decimals(&self) -> u8 { match self { Currency::NativeCurrency(native_currency) => native_currency.decimals(), Currency::Token(token) => token.decimals(), diff --git a/src/entities/ether.rs b/src/entities/ether.rs index 5f8fa57..7fba12c 100644 --- a/src/entities/ether.rs +++ b/src/entities/ether.rs @@ -1,5 +1,4 @@ -use super::{base_currency::BaseCurrency, currency::CurrencyTrait, token::Token}; -use crate::entities::weth9::WETH9; +use super::{base_currency::BaseCurrency, currency::CurrencyTrait, token::Token, weth9::WETH9}; use lazy_static::lazy_static; use std::{collections::HashMap, sync::Mutex}; @@ -11,7 +10,7 @@ lazy_static! { #[derive(Clone, PartialEq)] pub struct Ether { pub chain_id: u32, - pub decimals: u32, + pub decimals: u8, pub symbol: Option, pub name: Option, } @@ -54,7 +53,7 @@ impl BaseCurrency for Ether { self.chain_id } - fn decimals(&self) -> u32 { + fn decimals(&self) -> u8 { self.decimals } diff --git a/src/entities/fractions/currency_amount.rs b/src/entities/fractions/currency_amount.rs index 43e44c3..ac5fcd4 100644 --- a/src/entities/fractions/currency_amount.rs +++ b/src/entities/fractions/currency_amount.rs @@ -36,7 +36,7 @@ impl CurrencyAmount { denominator, meta: CurrencyMeta { currency, - decimal_scale: BigUint::from(10u64).pow(exponent), + decimal_scale: BigUint::from(10u64).pow(exponent as u32), }, } } @@ -156,7 +156,7 @@ impl FractionTrait> for CurrencyAmount { ) } - fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { self.as_fraction() .divide(&Fraction::new( self.meta.decimal_scale.to_bigint().unwrap(), @@ -166,7 +166,7 @@ impl FractionTrait> for CurrencyAmount { .to_significant(significant_digits, rounding) } - fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + fn to_fixed(&self, decimal_places: u8, rounding: Rounding) -> String { assert!(decimal_places <= self.meta.currency.decimals(), "DECIMALS"); self.as_fraction() .divide(&Fraction::new( diff --git a/src/entities/fractions/fraction.rs b/src/entities/fractions/fraction.rs index c84525d..eb65ef0 100644 --- a/src/entities/fractions/fraction.rs +++ b/src/entities/fractions/fraction.rs @@ -116,7 +116,7 @@ pub trait FractionTrait: Sized { .div(Decimal::from_str(&self.denominator().to_str_radix(10)).unwrap()) } - fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { assert!( significant_digits > 0, "Significant digits must be positive." @@ -125,16 +125,16 @@ pub trait FractionTrait: Sized { let rounding_strategy = to_rounding_strategy(rounding); let quotient = self .to_decimal() - .round_sf_with_strategy(significant_digits, rounding_strategy); + .round_sf_with_strategy(significant_digits as u32, rounding_strategy); quotient.unwrap().normalize().to_string() } - fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + fn to_fixed(&self, decimal_places: u8, rounding: Rounding) -> String { let rounding_strategy = to_rounding_strategy(rounding); let quotient = self .to_decimal() - .round_dp_with_strategy(decimal_places, rounding_strategy); + .round_dp_with_strategy(decimal_places as u32, rounding_strategy); format!("{:.1$}", quotient, decimal_places as usize) } diff --git a/src/entities/fractions/percent.rs b/src/entities/fractions/percent.rs index 2d08104..71f1208 100644 --- a/src/entities/fractions/percent.rs +++ b/src/entities/fractions/percent.rs @@ -38,13 +38,13 @@ impl FractionTrait<()> for Percent { &self.denominator } - fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { self.as_fraction() .multiply(&ONE_HUNDRED) .to_significant(significant_digits, rounding) } - fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + fn to_fixed(&self, decimal_places: u8, rounding: Rounding) -> String { self.as_fraction() .multiply(&ONE_HUNDRED) .to_fixed(decimal_places, rounding) diff --git a/src/entities/fractions/price.rs b/src/entities/fractions/price.rs index 3abde75..b0c2b58 100644 --- a/src/entities/fractions/price.rs +++ b/src/entities/fractions/price.rs @@ -61,12 +61,12 @@ where &self.denominator } - fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { self.adjusted_for_decimals() .to_significant(significant_digits, rounding) } - fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + fn to_fixed(&self, decimal_places: u8, rounding: Rounding) -> String { self.adjusted_for_decimals() .to_fixed(decimal_places, rounding) } @@ -84,8 +84,8 @@ where numerator: impl Into, ) -> Self { let scalar = Fraction::new( - BigInt::from(10).pow(base_currency.decimals()), - BigInt::from(10).pow(quote_currency.decimals()), + BigInt::from(10).pow(base_currency.decimals() as u32), + BigInt::from(10).pow(quote_currency.decimals() as u32), (), ); Self { diff --git a/src/entities/token.rs b/src/entities/token.rs index 4580023..752e644 100644 --- a/src/entities/token.rs +++ b/src/entities/token.rs @@ -6,7 +6,7 @@ use num_bigint::BigUint; pub struct Token { pub chain_id: u32, pub address: String, - pub decimals: u32, + pub decimals: u8, pub symbol: Option, pub name: Option, pub buy_fee_bps: Option, @@ -28,7 +28,7 @@ impl BaseCurrency for Token { self.chain_id } - fn decimals(&self) -> u32 { + fn decimals(&self) -> u8 { self.decimals } @@ -68,7 +68,7 @@ impl Token { pub fn new( chain_id: u32, address: String, - decimals: u32, + decimals: u8, symbol: Option, name: Option, buy_fee_bps: Option, @@ -107,7 +107,6 @@ impl Token { #[cfg(test)] mod tests { - use crate::entities::currency::Currency; //should test for neg chain_id or neg decimals or neg buy_fee or neg sell_fee, but the compiler will panic by itself, so no need use super::*; @@ -141,12 +140,12 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "DECIMALS")] fn test_expect_revert_overflow_dec() { let _token = Token::new( 4, ADDRESS_ONE.to_string(), - 256, + 255, Some("Test".to_string()), Some("Te".to_string()), None, @@ -155,8 +154,7 @@ mod tests { } #[test] - #[should_panic] - fn test_expect_revert_diff_chain_id() { + fn test_false_if_diff_chain_id() { let token = Token::new( 4, ADDRESS_ONE.to_string(), @@ -166,7 +164,6 @@ mod tests { None, None, ); - let token_1 = Token::new( 3, ADDRESS_ONE.to_string(), @@ -177,10 +174,7 @@ mod tests { None, ); - assert!( - token.equals(&Currency::Token(token_1)), - "SHOULD_FAILS_EVEN_THOUGH_CHAIN_ID_IS_DIFFERENT" - ); + assert!(!token.equals(&token_1)); } #[test] @@ -194,7 +188,6 @@ mod tests { None, None, ); - let token_1 = Token::new( 4, ADDRESS_ONE.to_string(), @@ -205,10 +198,7 @@ mod tests { None, ); - assert!( - token.equals(&Currency::Token(token_1)), - "true even if names differ" - ); + assert!(token.equals(&token_1), "true even if names differ"); } #[test] @@ -222,7 +212,6 @@ mod tests { None, None, ); - let token_1 = Token::new( 4, ADDRESS_ONE.to_string(), @@ -233,15 +222,11 @@ mod tests { None, ); - assert!( - token.equals(&Currency::Token(token_1)), - "true even if symbols differ" - ); + assert!(token.equals(&token_1), "true even if symbols differ"); } #[test] - #[should_panic] - fn test_expect_revert_diff_address() { + fn test_false_if_diff_address() { let token = Token::new( 4, ADDRESS_ONE.to_string(), @@ -251,7 +236,6 @@ mod tests { None, None, ); - let token_1 = Token::new( 4, DAI_MAINNET.to_string(), @@ -262,9 +246,21 @@ mod tests { None, ); + assert!(!token.equals(&token_1)); + } + + #[test] + fn test_true_if_diff_decimals() { assert!( - token.equals(&Currency::Token(token_1)), - "SHOULD_FAILS_EVEN_THOUGH_ADDRESS_IS_DIFFERENT" + Token::new(1, ADDRESS_ONE.to_string(), 9, None, None, None, None,).equals(&Token::new( + 1, + ADDRESS_ONE.to_string(), + 18, + None, + None, + None, + None, + )) ); } @@ -290,10 +286,69 @@ mod tests { None, ); - assert_eq!( - token.equals(&Currency::Token(token_1.clone())), - token_1.equals(&Currency::Token(token)), - "SHOULD_FAILS_EVEN_THOUGH_ADDRESS_IS_DIFFERENT, SHOULD ONLY REVERT FOR DIFFERENT CHAIN_ID" + assert_eq!(token.equals(&token_1), token_1.equals(&token)); + } + + #[test] + fn test_true_on_reference_equality() { + let token = Token::new( + 1, + ADDRESS_ONE.to_string(), + 18, + Some("Test".to_string()), + Some("Te".to_string()), + None, + None, + ); + + assert!(token.equals(&token)); + } + + #[test] + fn test_true_if_same_address() { + let token = Token::new( + 1, + ADDRESS_ONE.to_string(), + 9, + Some("abc".to_string()), + Some("def".to_string()), + None, + None, + ); + let token_1 = Token::new( + 1, + ADDRESS_ONE.to_string(), + 18, + Some("ghi".to_string()), + Some("jkl".to_string()), + None, + None, + ); + + assert!(token.equals(&token_1)); + } + + #[test] + fn test_true_if_one_token_is_checksummed_and_the_other_is_not() { + let token_a = Token::new( + 1, + DAI_MAINNET.to_string(), + 18, + Some("DAI".to_string()), + None, + None, + None, ); + let token_b = Token::new( + 1, + DAI_MAINNET.to_string().to_lowercase(), + 18, + Some("DAI".to_string()), + None, + None, + None, + ); + + assert!(token_a.equals(&token_b)); } }