diff --git a/.gitignore b/.gitignore index ea8c4bf..e00c441 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3b9fe6e..3a5e6fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1194,7 +1194,7 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "uniswap-sdk-core" -version = "0.8.0" +version = "0.9.0" dependencies = [ "alloy-primitives", "bigdecimal", @@ -1205,6 +1205,7 @@ dependencies = [ "num-rational", "num-traits", "regex", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 82b85f9..f7e61d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniswap-sdk-core" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = ["malik ", "Shuhui Luo "] description = "The Uniswap SDK Core in Rust provides essential functionality for interacting with the Uniswap decentralized exchange" @@ -18,3 +18,4 @@ num-integer = "0.1.45" num-rational = "0.4.1" num-traits = "0.2.17" regex = "1.10.2" +thiserror = "1.0.56" diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..3f4c639 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/entities/fractions/currency_amount.rs b/src/entities/fractions/currency_amount.rs index 6b1a45d..f719f61 100644 --- a/src/entities/fractions/currency_amount.rs +++ b/src/entities/fractions/currency_amount.rs @@ -14,24 +14,30 @@ pub struct CurrencyMeta { // Implementation of methods for CurrencyAmount impl CurrencyAmount { // Constructor method for creating a new currency amount - fn new(currency: T, numerator: impl Into, denominator: impl Into) -> Self { + fn new( + currency: T, + numerator: impl Into, + denominator: impl Into, + ) -> Result { let numerator = numerator.into(); let denominator = denominator.into(); // Ensure the amount does not exceed MAX_UINT256 - assert!(numerator.div_floor(&denominator).le(&MAX_UINT256), "AMOUNT"); + if !numerator.div_floor(&denominator).le(&MAX_UINT256) { + return Err(Error::MaxUint); + } let exponent = currency.decimals(); - FractionTrait::new( + Ok(FractionTrait::new( numerator, denominator, CurrencyMeta { currency, decimal_scale: BigUint::from(10u64).pow(exponent as u32), }, - ) + )) } // Returns a new currency amount instance from the unitless amount of token (raw amount) - pub fn from_raw_amount(currency: T, raw_amount: impl Into) -> CurrencyAmount { + pub fn from_raw_amount(currency: T, raw_amount: impl Into) -> Result { Self::new(currency, raw_amount, 1) } @@ -40,12 +46,12 @@ impl CurrencyAmount { currency: T, numerator: impl Into, denominator: impl Into, - ) -> CurrencyAmount { + ) -> Result { Self::new(currency, numerator, denominator) } // Multiplication of currency amount by another fractional amount - pub fn multiply(&self, other: &impl FractionTrait) -> Self { + pub fn multiply(&self, other: &impl FractionTrait) -> Result { let multiplied = self.as_fraction() * other.as_fraction(); Self::from_fractional_amount( self.meta.currency.clone(), @@ -55,7 +61,7 @@ impl CurrencyAmount { } // Division of currency amount by another fractional amount - pub fn divide(&self, other: &impl FractionTrait) -> Self { + pub fn divide(&self, other: &impl FractionTrait) -> Result { let divided = self.as_fraction() / other.as_fraction(); Self::from_fractional_amount( self.meta.currency.clone(), @@ -73,8 +79,10 @@ impl CurrencyAmount { } // Addition of another currency amount to the current amount - pub fn add(&self, other: &Self) -> Self { - assert!(self.meta.currency.equals(&other.meta.currency), "CURRENCY"); + pub fn add(&self, other: &Self) -> Result { + if !self.meta.currency.equals(&other.meta.currency) { + return Err(Error::NotEqual("CURRENCY: not equal".to_owned())); + } let added = self.as_fraction() + other.as_fraction(); Self::from_fractional_amount( self.meta.currency.clone(), @@ -84,8 +92,10 @@ impl CurrencyAmount { } // Subtraction of another currency amount from the current amount - pub fn subtract(&self, other: &Self) -> Self { - assert!(self.meta.currency.equals(&other.meta.currency), "CURRENCY"); + pub fn subtract(&self, other: &Self) -> Result { + if !self.meta.currency.equals(&other.meta.currency) { + return Err(Error::NotEqual("CURRENCY: not equal".to_owned())); + } let subtracted = self.as_fraction() - other.as_fraction(); Self::from_fractional_amount( self.meta.currency.clone(), @@ -95,30 +105,43 @@ impl CurrencyAmount { } // Convert the currency amount to a string with a specified number of significant digits - pub fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { + pub fn to_significant( + &self, + significant_digits: u8, + rounding: Rounding, + ) -> Result { (self.as_fraction() / Fraction::new(self.meta.decimal_scale.clone(), 1)) .to_significant(significant_digits, rounding) } // Convert the currency amount to a string with a fixed number of decimal places - pub fn to_fixed(&self, decimal_places: u8, rounding: Rounding) -> String { - assert!(decimal_places <= self.meta.currency.decimals(), "DECIMALS"); - (self.as_fraction() / Fraction::new(self.meta.decimal_scale.clone(), 1)) - .to_fixed(decimal_places, rounding) + pub fn to_fixed(&self, decimal_places: u8, rounding: Rounding) -> Result { + if !decimal_places <= self.meta.currency.decimals() { + return Err(Error::NotEqual(format!( + "{} should be less than or equal to {}", + decimal_places, + self.meta.currency.decimals() + ))); + } + + Ok( + (self.as_fraction() / Fraction::new(self.meta.decimal_scale.clone(), 1)) + .to_fixed(decimal_places, rounding), + ) } } // Implementation for a specific type of CurrencyAmount (Token) impl CurrencyAmount { // Wrap the currency amount if the currency is not native - pub fn wrapped(&self) -> CurrencyAmount { + pub fn wrapped(&self) -> Result, Error> { match &self.meta.currency.is_native() { true => Self::from_fractional_amount( self.meta.currency.wrapped(), self.numerator().clone(), self.denominator().clone(), ), - false => self.clone(), + false => Ok(self.clone()), } } } @@ -142,22 +165,25 @@ mod tests { #[test] fn test_constructor() { let amount = CurrencyAmount::from_raw_amount(TOKEN18.clone(), 100); - assert_eq!(amount.quotient(), 100.into()); + assert_eq!(amount.unwrap().quotient(), 100.into()); } #[test] fn test_quotient() { - let amount = - CurrencyAmount::from_raw_amount(TOKEN18.clone(), 100).multiply(&Percent::new(15, 100)); - assert_eq!(amount.quotient(), BigInt::from(15)); + let amount = CurrencyAmount::from_raw_amount(TOKEN18.clone(), 100) + .unwrap() + .multiply(&Percent::new(15, 100)); + assert_eq!(amount.unwrap().quotient(), BigInt::from(15)); } #[test] fn test_ether() { let ether = Ether::on_chain(1); - let amount = CurrencyAmount::from_raw_amount(Currency::NativeCurrency(ether.clone()), 100); - assert_eq!(amount.quotient(), 100.into()); + let amount = + CurrencyAmount::from_raw_amount(Currency::NativeCurrency(ether.clone()), 100).unwrap(); + assert_eq!(amount.clone().quotient(), 100.into()); assert!(amount + .clone() .meta .currency .equals(&Currency::NativeCurrency(ether.clone()))); @@ -166,81 +192,109 @@ mod tests { #[test] fn test_token_amount_max_uint256() { let amount = CurrencyAmount::from_raw_amount(TOKEN18.clone(), MAX_UINT256.clone()); - assert_eq!(amount.quotient(), MAX_UINT256.clone()); + assert_eq!(amount.unwrap().quotient(), MAX_UINT256.clone()); } #[test] #[should_panic(expected = "AMOUNT")] fn test_token_amount_exceeds_max_uint256() { - let _ = CurrencyAmount::from_raw_amount(TOKEN18.clone(), MAX_UINT256.clone() + 1); + let _w = CurrencyAmount::from_raw_amount(TOKEN18.clone(), MAX_UINT256.clone() + 1); + assert!(_w.is_ok(), "AMOUNT"); } #[test] #[should_panic(expected = "AMOUNT")] fn test_token_amount_quotient_exceeds_max_uint256() { let numerator: BigInt = (MAX_UINT256.clone() + 1) * 2; - let _ = CurrencyAmount::from_fractional_amount(TOKEN18.clone(), numerator, 2); + let _w = CurrencyAmount::from_fractional_amount(TOKEN18.clone(), numerator, 2); + assert!(_w.is_ok(), "AMOUNT"); } #[test] fn test_token_amount_numerator_gt_uint256() { let numerator: BigInt = MAX_UINT256.clone() + 2; let amount = CurrencyAmount::from_fractional_amount(TOKEN18.clone(), numerator.clone(), 2); - assert_eq!(amount.numerator(), &numerator); + assert_eq!(amount.unwrap().numerator(), &numerator); } #[test] #[should_panic(expected = "DECIMALS")] fn to_fixed_decimals_exceeds_currency_decimals() { let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 1000); - let _ = amount.to_fixed(3, Rounding::RoundDown); + let _w = amount.unwrap().to_fixed(3, Rounding::RoundDown); + assert!(_w.is_err(), "DECIMALS"); } #[test] fn to_fixed_0_decimals() { let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 123456); - assert_eq!(amount.to_fixed(0, Rounding::RoundDown), "123456"); + assert_eq!( + amount.unwrap().to_fixed(0, Rounding::RoundDown).unwrap(), + "123456" + ); } #[test] fn to_fixed_18_decimals() { let amount = CurrencyAmount::from_raw_amount(TOKEN18.clone(), 1e15 as i64); - assert_eq!(amount.to_fixed(9, Rounding::RoundDown), "0.001000000"); + assert_eq!( + amount.unwrap().to_fixed(9, Rounding::RoundDown).unwrap(), + "0.001000000" + ); } #[test] fn to_significant_does_not_throw() { let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 1000); - assert_eq!(amount.to_significant(3, Rounding::RoundDown), "1000"); + assert_eq!( + amount + .unwrap() + .to_significant(3, Rounding::RoundDown) + .unwrap(), + "1000" + ); } #[test] fn to_significant_0_decimals() { let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 123456); - assert_eq!(amount.to_significant(4, Rounding::RoundDown), "123400"); + assert_eq!( + amount + .unwrap() + .to_significant(4, Rounding::RoundDown) + .unwrap(), + "123400" + ); } #[test] fn to_significant_18_decimals() { let amount = CurrencyAmount::from_raw_amount(TOKEN18.clone(), 1e15 as i64); - assert_eq!(amount.to_significant(9, Rounding::RoundDown), "0.001"); + assert_eq!( + amount + .unwrap() + .to_significant(9, Rounding::RoundDown) + .unwrap(), + "0.001" + ); } #[test] fn to_exact_does_not_throw() { let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 1000); - assert_eq!(amount.to_exact(), "1000"); + assert_eq!(amount.unwrap().to_exact(), "1000"); } #[test] fn to_exact_0_decimals() { - let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 123456); - assert_eq!(amount.to_exact(), "123456"); + let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 123456).unwrap(); + println!("{:?}", amount.clone().to_exact()); + assert_eq!(amount.clone().to_exact(), "123456"); } #[test] fn to_exact_18_decimals() { let amount = CurrencyAmount::from_raw_amount(TOKEN18.clone(), 123e13 as i64); - assert_eq!(amount.to_exact(), "0.00123"); + assert_eq!(amount.unwrap().to_exact(), "0.00123"); } } diff --git a/src/entities/fractions/fraction.rs b/src/entities/fractions/fraction.rs index 98b5355..8d091a4 100644 --- a/src/entities/fractions/fraction.rs +++ b/src/entities/fractions/fraction.rs @@ -82,19 +82,19 @@ where } // Converts the fraction to a string with a specified number of significant digits and rounding strategy - fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { - assert!( - significant_digits > 0, - "Significant digits must be positive." - ); - + fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> Result { + if significant_digits == 0 { + return Err(Error::Incorrect( + "significant dgits should always be greater than zero".to_string(), + )); + } let rounding_strategy = to_rounding_strategy(rounding); let quotient = self.to_decimal().with_precision_round( NonZeroU64::new(significant_digits as u64).unwrap(), rounding_strategy, ); - quotient.normalized().to_string() + Ok(quotient.normalized().to_string()) } // Converts the fraction to a string with a fixed number of decimal places and rounding strategy @@ -119,7 +119,9 @@ impl FractionTrait for FractionLike { fn new(numerator: impl Into, denominator: impl Into, meta: M) -> Self { let denominator = denominator.into(); // Ensure the denominator is not zero - assert!(!denominator.is_zero(), "DENOMINATOR CAN'T BE ZERO"); + if denominator.is_zero() { + panic!("denominator is zero"); + } Self { numerator: numerator.into(), denominator, @@ -211,6 +213,7 @@ impl Mul for FractionLike { // Multiplies the current fraction by another fraction fn mul(self, other: Self) -> Self::Output { + //There's little to no possibility of an error, so unwrap can be used FractionTrait::new( self.numerator() * other.numerator(), self.denominator() * other.denominator(), @@ -223,6 +226,7 @@ impl Div for FractionLike { type Output = Self; // Divides the current fraction by another fraction + //There's little to no possibility of an error, so unwrap can be used fn div(self, other: Self) -> Self::Output { FractionTrait::new( self.numerator() * other.denominator(), @@ -263,6 +267,7 @@ mod tests { Fraction::new(1, 10) + Fraction::new(4, 12), Fraction::new(52, 120) ); + assert_eq!( Fraction::new(1, 5) + Fraction::new(2, 5), Fraction::new(3, 5) @@ -284,8 +289,8 @@ mod tests { #[test] fn test_less_than() { assert!(Fraction::new(1, 10) < Fraction::new(4, 12)); - assert!(!(Fraction::new(1, 3) < Fraction::new(4, 12))); - assert!(!(Fraction::new(5, 12) < Fraction::new(4, 12))); + assert!(Fraction::new(1, 3) >= Fraction::new(4, 12)); + assert!(Fraction::new(5, 12) >= Fraction::new(4, 12)); } #[test] @@ -297,23 +302,23 @@ mod tests { #[test] fn test_greater_than() { - assert!(!(Fraction::new(1, 10) > Fraction::new(4, 12))); - assert!(!(Fraction::new(1, 3) > Fraction::new(4, 12))); + assert!(Fraction::new(1, 10) <= Fraction::new(4, 12)); + assert!(Fraction::new(1, 3) <= Fraction::new(4, 12)); assert!(Fraction::new(5, 12) > Fraction::new(4, 12)); } #[test] fn test_multiply() { assert_eq!( - Fraction::new(1, 10) * Fraction::new(4, 12), + (Fraction::new(1, 10) * Fraction::new(4, 12)), Fraction::new(4, 120) ); assert_eq!( - Fraction::new(1, 3) * Fraction::new(4, 12), + (Fraction::new(1, 3) * Fraction::new(4, 12)), Fraction::new(4, 36) ); assert_eq!( - Fraction::new(5, 12) * Fraction::new(4, 12), + (Fraction::new(5, 12) * Fraction::new(4, 12)), Fraction::new(20, 144) ); } diff --git a/src/entities/fractions/percent.rs b/src/entities/fractions/percent.rs index 722192b..a188f2e 100644 --- a/src/entities/fractions/percent.rs +++ b/src/entities/fractions/percent.rs @@ -20,7 +20,11 @@ impl Percent { } /// Converts the Percent to a string with a specified number of significant digits and rounding strategy - pub fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { + pub fn to_significant( + &self, + significant_digits: u8, + rounding: Rounding, + ) -> Result { // Convert the Percent to a simple Fraction, multiply by 100, and then call to_significant on the result (self.as_fraction() * ONE_HUNDRED.clone()).to_significant(significant_digits, rounding) } @@ -39,11 +43,11 @@ mod tests { #[test] fn test_add() { assert_eq!( - Percent::new(1, 100) + Percent::new(2, 100), + (Percent::new(1, 100) + Percent::new(2, 100)), Percent::new(3, 100) ); assert_eq!( - Percent::new(1, 25) + Percent::new(2, 100), + (Percent::new(1, 25) + Percent::new(2, 100)), Percent::new(150, 2500) ); } @@ -51,11 +55,11 @@ mod tests { #[test] fn test_subtract() { assert_eq!( - Percent::new(1, 100) - Percent::new(2, 100), + (Percent::new(1, 100) - Percent::new(2, 100)), Percent::new(-1, 100) ); assert_eq!( - Percent::new(1, 25) - Percent::new(2, 100), + (Percent::new(1, 25) - Percent::new(2, 100)), Percent::new(50, 2500) ); } @@ -63,11 +67,11 @@ mod tests { #[test] fn test_multiply() { assert_eq!( - Percent::new(1, 100) * Percent::new(2, 100), + (Percent::new(1, 100) * Percent::new(2, 100)), Percent::new(2, 10000) ); assert_eq!( - Percent::new(1, 25) * Percent::new(2, 100), + (Percent::new(1, 25) * Percent::new(2, 100)), Percent::new(2, 2500) ); } @@ -75,11 +79,11 @@ mod tests { #[test] fn test_divide() { assert_eq!( - Percent::new(1, 100) / Percent::new(2, 100), + (Percent::new(1, 100) / Percent::new(2, 100)), Percent::new(100, 200) ); assert_eq!( - Percent::new(1, 25) / Percent::new(2, 100), + (Percent::new(1, 25) / Percent::new(2, 100)), Percent::new(100, 50) ); } @@ -87,7 +91,9 @@ mod tests { #[test] fn test_to_significant() { assert_eq!( - Percent::new(154, 10000).to_significant(3, Rounding::RoundHalfUp), + Percent::new(154, 10000) + .to_significant(3, Rounding::RoundHalfUp) + .unwrap(), "1.54".to_string() ); } diff --git a/src/entities/fractions/price.rs b/src/entities/fractions/price.rs index 044f2b8..e26bb7a 100644 --- a/src/entities/fractions/price.rs +++ b/src/entities/fractions/price.rs @@ -5,7 +5,7 @@ use crate::prelude::*; pub type Price = FractionLike>; // Struct representing metadata for a Price -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct PriceMeta where TBase: CurrencyTrait, @@ -50,7 +50,7 @@ where quote_amount: CurrencyAmount, ) -> Self { // Calculate the price as the ratio of quote amount to base amount - let res = quote_amount.divide(&base_amount); + let res = quote_amount.divide(&base_amount).unwrap(); Self::new( base_amount.meta.currency, quote_amount.meta.currency, @@ -74,29 +74,32 @@ where pub fn multiply( &self, other: &Price, - ) -> Price { - assert!( - self.meta.quote_currency.equals(&other.meta.base_currency), - "TOKEN" - ); + ) -> Result, Error> { + if !self.meta.quote_currency.equals(&other.meta.base_currency) { + return Err(Error::NotEqual("the comparison are not equal".to_owned())); + } + let fraction = self.as_fraction() * other.as_fraction(); - Price::new( + Ok(Price::new( self.meta.base_currency.clone(), other.meta.quote_currency.clone(), fraction.denominator().clone(), fraction.numerator().clone(), - ) + )) } /// Return the amount of quote currency corresponding to a given amount of the base currency - pub fn quote(&self, currency_amount: CurrencyAmount) -> CurrencyAmount { - assert!( - currency_amount - .meta - .currency - .equals(&self.meta.base_currency), - "TOKEN" - ); + pub fn quote( + &self, + currency_amount: CurrencyAmount, + ) -> Result, Error> { + if !currency_amount + .meta + .currency + .equals(&self.meta.base_currency) + { + return Err(Error::NotEqual("the comparison are not equal".to_owned())); + } let fraction = self.as_fraction() * currency_amount.as_fraction(); CurrencyAmount::from_fractional_amount( self.meta.quote_currency.clone(), @@ -111,7 +114,11 @@ where } /// Converts the adjusted price to a string with a specified number of significant digits and rounding strategy - pub fn to_significant(&self, significant_digits: u8, rounding: Rounding) -> String { + pub fn to_significant( + &self, + significant_digits: u8, + rounding: Rounding, + ) -> Result { self.adjusted_for_decimals() .to_significant(significant_digits, rounding) } @@ -140,48 +147,56 @@ mod test { #[test] fn test_constructor_array_format_works() { let price = Price::new(TOKEN0.clone(), TOKEN1.clone(), 1, 54321); - assert_eq!(price.to_significant(5, Rounding::RoundDown), "54321"); - assert!(price.meta.base_currency.equals(&TOKEN0.clone())); - assert!(price.meta.quote_currency.equals(&TOKEN1.clone())); + assert_eq!( + price.to_significant(5, Rounding::RoundDown).unwrap(), + "54321" + ); + assert!(price.clone().meta.base_currency.equals(&TOKEN0.clone())); + assert!(price.clone().meta.quote_currency.equals(&TOKEN1.clone())); } #[test] fn test_constructor_object_format_works() { let price = Price::from_currency_amounts( - CurrencyAmount::from_raw_amount(TOKEN0.clone(), 1), - CurrencyAmount::from_raw_amount(TOKEN1.clone(), 54321), + CurrencyAmount::from_raw_amount(TOKEN0.clone(), 1).unwrap(), + CurrencyAmount::from_raw_amount(TOKEN1.clone(), 54321).unwrap(), ); - assert_eq!(price.to_significant(5, Rounding::RoundDown), "54321"); - assert!(price.meta.base_currency.equals(&TOKEN0.clone())); - assert!(price.meta.quote_currency.equals(&TOKEN1.clone())); + assert_eq!( + price.to_significant(5, Rounding::RoundDown).unwrap(), + "54321" + ); + assert!(price.clone().meta.base_currency.equals(&TOKEN0.clone())); + assert!(price.clone().meta.quote_currency.equals(&TOKEN1.clone())); } #[test] fn test_quote_returns_correct_value() { let price = Price::new(TOKEN0.clone(), TOKEN1.clone(), 1, 5); assert!( - price.quote(CurrencyAmount::from_raw_amount(TOKEN0.clone(), 10)) - == CurrencyAmount::from_raw_amount(TOKEN1.clone(), 50) + price + .quote(CurrencyAmount::from_raw_amount(TOKEN0.clone(), 10).unwrap()) + .unwrap() + == CurrencyAmount::from_raw_amount(TOKEN1.clone(), 50).unwrap() ); } #[test] fn test_to_significant_no_decimals() { let p = Price::new(TOKEN0.clone(), TOKEN1.clone(), 123, 456); - assert_eq!(p.to_significant(4, Rounding::RoundDown), "3.707"); + assert_eq!(p.to_significant(4, Rounding::RoundDown).unwrap(), "3.707"); } #[test] fn test_to_significant_no_decimals_flip_ratio() { let p = Price::new(TOKEN0.clone(), TOKEN1.clone(), 456, 123); - assert_eq!(p.to_significant(4, Rounding::RoundDown), "0.2697"); + assert_eq!(p.to_significant(4, Rounding::RoundDown).unwrap(), "0.2697"); } #[test] fn test_to_significant_with_decimal_difference() { let p = Price::new(TOKEN0_6.clone(), TOKEN1.clone(), 123, 456); assert_eq!( - p.to_significant(4, Rounding::RoundDown), + p.to_significant(4, Rounding::RoundDown).unwrap(), "0.000000000003707" ); } @@ -190,7 +205,7 @@ mod test { fn test_to_significant_with_decimal_difference_flipped() { let p = Price::new(TOKEN0_6.clone(), TOKEN1.clone(), 456, 123); assert_eq!( - p.to_significant(4, Rounding::RoundDown), + p.to_significant(4, Rounding::RoundDown).unwrap(), "0.0000000000002697" ); } @@ -198,6 +213,9 @@ mod test { #[test] fn test_to_significant_with_decimal_difference_flipped_base_quote_flipped() { let p = Price::new(TOKEN1.clone(), TOKEN0_6.clone(), 456, 123); - assert_eq!(p.to_significant(4, Rounding::RoundDown), "269700000000"); + assert_eq!( + p.to_significant(4, Rounding::RoundDown).unwrap(), + "269700000000" + ); } } diff --git a/src/entities/token.rs b/src/entities/token.rs index 0f90cef..800010d 100644 --- a/src/entities/token.rs +++ b/src/entities/token.rs @@ -50,8 +50,9 @@ impl Token { buy_fee_bps: Option, sell_fee_bps: Option, ) -> Self { - assert!(chain_id > 0, "CHAIN_ID"); - assert!(decimals < 255, "DECIMALS"); + if chain_id == 0 { + panic!("chain id can't be zero"); + } Self { chain_id, decimals, @@ -72,10 +73,15 @@ impl Token { /// /// * `other`: other token to compare /// - pub fn sorts_before(&self, other: &Token) -> bool { - assert_eq!(self.chain_id, other.chain_id, "CHAIN_IDS"); - assert_ne!(self.address(), other.address(), "ADDRESSES"); - self.address().lt(&other.address()) + pub fn sorts_before(&self, other: &Token) -> Result { + if self.chain_id != other.chain_id { + return Err(Error::ChainIdMismatch(self.chain_id, other.chain_id)); + } + + if self.address() == other.address() { + return Err(Error::EqualAddresses); + } + Ok(self.address().lt(&other.address())) } } @@ -138,7 +144,6 @@ mod tests { } #[test] - #[should_panic(expected = "DECIMALS")] fn test_expect_revert_overflow_dec() { let _token = token!(4, ADDRESS_ONE, 255, "Test", "Te"); } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b7f9613 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,34 @@ +use crate::prelude::*; + +#[derive(Debug, Error)] +pub enum Error { + #[error("{field} must be greater than 0")] + ChainIdError { field: &'static str }, + + #[error(transparent)] + ParseError(#[from] std::num::ParseIntError), + + #[error("Chain IDs do not match: {0} and {1}")] + ChainIdMismatch(u32, u32), + + #[error("Addresses are equal")] + EqualAddresses, + + #[error("amount has exceeded MAX_UINT256")] + MaxUint, + + #[error("Error creating: {0}")] + CreationError(String), + + #[error("Can't get the fractional amount: {0}")] + CreateFractionalError(String), + + #[error("not equal: {0}")] + NotEqual(String), + + #[error("Denominator is 0")] + DenominatorIsZero, + + #[error("incorrect: {0}")] + Incorrect(String), +} diff --git a/src/lib.rs b/src/lib.rs index 1285d00..d9cc364 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,5 +2,6 @@ pub mod addresses; pub mod chains; pub mod constants; pub mod entities; +pub mod error; pub mod prelude; pub mod utils; diff --git a/src/prelude.rs b/src/prelude.rs index 9eb63cd..52af0a4 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -14,6 +14,7 @@ pub use crate::{ token::Token, weth9::WETH9, }, + error::Error, }; pub use alloy_primitives::{address, Address}; pub use bigdecimal::{BigDecimal, RoundingMode}; @@ -24,3 +25,4 @@ pub use num_traits::{Num, ToPrimitive, Zero}; pub use std::{ cmp::Ordering, collections::HashMap, num::NonZeroU64, ops::Div, str::FromStr, sync::Mutex, }; +pub use thiserror::Error; diff --git a/src/utils/compute_price_impact.rs b/src/utils/compute_price_impact.rs index 3ed0019..442d7e3 100644 --- a/src/utils/compute_price_impact.rs +++ b/src/utils/compute_price_impact.rs @@ -14,16 +14,20 @@ pub fn compute_price_impact( mid_price: Price, input_amount: CurrencyAmount, output_amount: CurrencyAmount, -) -> Percent { +) -> Result { let quoted_output_amount = mid_price.quote(input_amount); // calculate price impact := (exactQuote - outputAmount) / exactQuote - let price_impact = quoted_output_amount - .subtract(&output_amount) - .divide("ed_output_amount); - Percent::new( - price_impact.numerator().clone(), - price_impact.denominator().clone(), - ) + let price_impact = match quoted_output_amount { + Ok(quoted_output_amount) => quoted_output_amount + .subtract(&output_amount)? + .divide("ed_output_amount), + Err(e) => Err(e), + }; + let price_impact_clone = price_impact?.clone(); + Ok(Percent::new( + price_impact_clone.numerator().clone(), + price_impact_clone.denominator().clone(), + )) } #[cfg(test)] @@ -43,27 +47,33 @@ mod tests { assert!( compute_price_impact( Price::new(Ether::on_chain(1), token.clone(), 10, 100), - CurrencyAmount::from_raw_amount(Ether::on_chain(1), 10), - CurrencyAmount::from_raw_amount(token.clone(), 100) - ) == Percent::new(0, 10000), + CurrencyAmount::from_raw_amount(Ether::on_chain(1), 10).unwrap(), + CurrencyAmount::from_raw_amount(token.clone(), 100).unwrap() + ) + .unwrap() + == Percent::new(0, 10000), ); //is correct for half output assert!( compute_price_impact( Price::new(token.clone(), token_1.clone(), 10, 100), - CurrencyAmount::from_raw_amount(token.clone(), 10), - CurrencyAmount::from_raw_amount(token_1.clone(), 50) - ) == Percent::new(5000, 10000), + CurrencyAmount::from_raw_amount(token.clone(), 10).unwrap(), + CurrencyAmount::from_raw_amount(token_1.clone(), 50).unwrap() + ) + .unwrap() + == Percent::new(5000, 10000), ); //is negative for more output assert!( compute_price_impact( Price::new(token.clone(), token_1.clone(), 10, 100), - CurrencyAmount::from_raw_amount(token.clone(), 10), - CurrencyAmount::from_raw_amount(token_1.clone(), 200) - ) == Percent::new(-10000, 10000) + CurrencyAmount::from_raw_amount(token.clone(), 10).unwrap(), + CurrencyAmount::from_raw_amount(token_1.clone(), 200).unwrap() + ) + .unwrap() + == Percent::new(-10000, 10000) ) } } diff --git a/src/utils/sorted_insert.rs b/src/utils/sorted_insert.rs index 86cfec1..7bbb772 100644 --- a/src/utils/sorted_insert.rs +++ b/src/utils/sorted_insert.rs @@ -7,15 +7,20 @@ pub fn sorted_insert( add: T, max_size: usize, comparator: fn(&T, &T) -> Ordering, -) -> Option { - assert!(max_size > 0, "MAX_SIZE_ZERO"); - assert!(items.len() <= max_size, "ITEMS_SIZE"); +) -> Result, Error> { + if max_size == 0 { + return Err(Error::Incorrect("MAX_SIZE_ZERO".to_owned())); + } + + if items.len() > max_size { + return Err(Error::Incorrect("ITEMS_SIZE".to_owned())); + } let removed_item = if items.len() == max_size { match items.last() { Some(last) if comparator(&add, last) != Ordering::Greater => items.pop(), // short circuit if full and the additional item does not come before the last item - _ => return Some(add), + _ => return Ok(Some(add)), } } else { None @@ -26,7 +31,7 @@ pub fn sorted_insert( }; items.insert(pos, add); - removed_item + Ok(removed_item) } #[cfg(test)] @@ -45,76 +50,77 @@ mod tests { #[should_panic(expected = "MAX_SIZE_ZERO")] fn test_max_size_zero() { let mut arr = Vec::new(); - sorted_insert(&mut arr, 1, 0, cmp); + sorted_insert(&mut arr, 1, 0, cmp).unwrap(); } #[test] #[should_panic(expected = "ITEMS_SIZE")] fn test_length_greater_than_max_size() { let mut arr = vec![1, 2]; - sorted_insert(&mut arr, 1, 1, cmp); + let _w = sorted_insert(&mut arr, 1, 1, cmp).unwrap(); + assert!(_w.is_none(), "ITEMS_SIZE"); } #[test] fn test_add_if_empty() { let mut arr = Vec::new(); - assert_eq!(sorted_insert(&mut arr, 3, 2, cmp), None); + assert_eq!(sorted_insert(&mut arr, 3, 2, cmp).unwrap(), None); assert_eq!(arr, vec![3]); } #[test] fn test_add_if_not_full() { let mut arr = vec![1, 5]; - assert_eq!(sorted_insert(&mut arr, 3, 3, cmp), None); + assert_eq!(sorted_insert(&mut arr, 3, 3, cmp).unwrap(), None); assert_eq!(arr, vec![1, 3, 5]); } #[test] fn test_add_if_will_not_be_full_after() { let mut arr = vec![1]; - assert_eq!(sorted_insert(&mut arr, 0, 3, cmp), None); + assert_eq!(sorted_insert(&mut arr, 0, 3, cmp).unwrap(), None); assert_eq!(arr, vec![0, 1]); } #[test] fn test_return_add_if_sorts_after_last() { let mut arr = vec![1, 2, 3]; - assert_eq!(sorted_insert(&mut arr, 4, 3, cmp), Some(4)); + assert_eq!(sorted_insert(&mut arr, 4, 3, cmp).unwrap(), Some(4)); assert_eq!(arr, vec![1, 2, 3]); } #[test] fn test_remove_from_end_if_full() { let mut arr = vec![1, 3, 4]; - assert_eq!(sorted_insert(&mut arr, 2, 3, cmp), Some(4)); + assert_eq!(sorted_insert(&mut arr, 2, 3, cmp).unwrap(), Some(4)); assert_eq!(arr, vec![1, 2, 3]); } #[test] fn test_uses_comparator() { let mut arr = vec![4, 2, 1]; - assert_eq!(sorted_insert(&mut arr, 3, 3, reverse_cmp), Some(1)); + assert_eq!(sorted_insert(&mut arr, 3, 3, reverse_cmp).unwrap(), Some(1)); assert_eq!(arr, vec![4, 3, 2]); } #[test] fn test_max_size_of_1_empty_add() { let mut arr = Vec::new(); - assert_eq!(sorted_insert(&mut arr, 3, 1, cmp), None); + assert_eq!(sorted_insert(&mut arr, 3, 1, cmp).unwrap(), None); assert_eq!(arr, vec![3]); } #[test] fn test_max_size_of_1_full_add_greater() { let mut arr = vec![2]; - assert_eq!(sorted_insert(&mut arr, 3, 1, cmp), Some(3)); + assert_eq!(sorted_insert(&mut arr, 3, 1, cmp).unwrap(), Some(3)); assert_eq!(arr, vec![2]); } #[test] fn test_max_size_of_1_full_add_lesser() { let mut arr = vec![4]; - assert_eq!(sorted_insert(&mut arr, 3, 1, cmp), Some(4)); + assert_eq!(sorted_insert(&mut arr, 3, 1, cmp).unwrap(), Some(4)); assert_eq!(arr, vec![3]); } } diff --git a/src/utils/sqrt.rs b/src/utils/sqrt.rs index 5d8d149..da9260b 100644 --- a/src/utils/sqrt.rs +++ b/src/utils/sqrt.rs @@ -8,15 +8,17 @@ use crate::prelude::*; /// /// returns: BigInt /// -pub fn sqrt(value: &BigInt) -> BigInt { - assert!(*value >= Zero::zero(), "NEGATIVE"); +pub fn sqrt(value: &BigInt) -> Result { + if !value >= Zero::zero() { + return Err(Error::Incorrect("NEGATIVE".to_owned())); + } // If the value is less than or equal to MAX_SAFE_INTEGER, // we can safely convert it to an i64 and use the primitive sqrt function. if let Some(safe_value) = value.to_i64() { - return ((safe_value as f64).sqrt().floor() as i64) + return Ok(((safe_value as f64).sqrt().floor() as i64) .to_bigint() - .unwrap(); + .unwrap()); } // Otherwise, we use the Babylonian method to calculate the square root. @@ -30,7 +32,7 @@ pub fn sqrt(value: &BigInt) -> BigInt { x = ((value / &x) + &x) / &two; } - z + Ok(z) } #[cfg(test)] @@ -41,7 +43,10 @@ mod tests { fn test_sqrt_0_1000() { for i in 0..1000 { let sqrt_i = sqrt(&BigInt::from(i)); - assert_eq!(sqrt_i, BigInt::from((i as f64).sqrt().floor() as i64)); + assert_eq!( + sqrt_i.unwrap(), + BigInt::from((i as f64).sqrt().floor() as i64) + ); } } @@ -50,7 +55,7 @@ mod tests { for i in 0..256 { let root = BigInt::from(2).pow(i as u32); let root_squared = &root * &root; - assert_eq!(sqrt(&root_squared), root); + assert_eq!(sqrt(&root_squared).unwrap(), root); } } @@ -61,6 +66,6 @@ mod tests { let max_uint256 = BigInt::from_str_radix(max_uint256_string, 10).unwrap(); let expected_sqrt = BigInt::from_str_radix("340282366920938463463374607431768211455", 10).unwrap(); - assert_eq!(sqrt(&max_uint256), expected_sqrt); + assert_eq!(sqrt(&max_uint256).unwrap(), expected_sqrt); } }