diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..5e81d31 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,37 @@ +name: Rust + +on: + push: + branches: + - master + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: Rust Tests + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Cache Cargo registry + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + - name: Build + run: cargo build --verbose + + - name: Run tests + run: cargo test --verbose diff --git a/Cargo.lock b/Cargo.lock index 1d77816..4baa3ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,7 +640,7 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "uniswap-sdk-core-rust" -version = "0.1.1" +version = "0.2.0" dependencies = [ "eth_checksum", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 5282d6e..02a3c05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniswap-sdk-core-rust" -version = "0.1.1" +version = "0.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/entities/base_currency.rs b/src/entities/base_currency.rs index 5f5135d..20d17e9 100644 --- a/src/entities/base_currency.rs +++ b/src/entities/base_currency.rs @@ -1,29 +1,28 @@ +use super::{currency::CurrencyTrait, token::Token}; + /// A currency is any fungible financial instrument, including Ether, all ERC20 tokens, and other chain-native currencies -#[derive(Clone, PartialEq)] -pub struct BaseCurrency { - pub chain_id: u32, - pub decimals: u32, - pub name: Option, - pub symbol: Option, - pub is_native: bool, -} +pub trait BaseCurrency: Clone { + /// The chain ID on which this currency resides + fn chain_id(&self) -> u32; + + /// The decimals used in representing currency amounts + fn decimals(&self) -> u32; + + /// The symbol of the currency, i.e. a short textual non-unique identifier + fn symbol(&self) -> Option; -impl BaseCurrency { - pub fn new(chain_id: u32, decimals: u32, name: Option, symbol: Option) -> Self { - assert!(chain_id > 0, "CHAIN_ID"); - assert!(decimals < 255, "DECIMALS"); + /// The name of the currency, i.e. a descriptive textual non-unique identifier + fn name(&self) -> Option; - Self { - chain_id, - decimals, - name, - symbol, - is_native: Self::is_native(), - } - } + /// Returns whether this currency is functionally equivalent to the other currency + /// + /// # Arguments + /// + /// * `other`: the other currency + /// + fn equals(&self, other: &impl CurrencyTrait) -> bool; - /// Returns whether the currency is native to the chain and must be wrapped (e.g. Ether) - pub fn is_native() -> bool { - true - } + /// Return the wrapped version of this currency that can be used with the Uniswap contracts. + /// Currencies must implement this to be used in Uniswap + fn wrapped(&self) -> Token; } diff --git a/src/entities/currency.rs b/src/entities/currency.rs index c0de993..c2c0919 100644 --- a/src/entities/currency.rs +++ b/src/entities/currency.rs @@ -1,7 +1,74 @@ -use super::{native_currency::NativeCurrency, token::Token}; +use super::{base_currency::BaseCurrency, ether::Ether, token::Token}; #[derive(Clone, PartialEq)] pub enum Currency { - NativeCurrency(NativeCurrency), + NativeCurrency(Ether), Token(Token), } + +pub trait CurrencyTrait: BaseCurrency { + /// Returns whether the currency is native to the chain and must be wrapped (e.g. Ether) + fn is_native(&self) -> bool; + + fn address(&self) -> String; +} + +impl CurrencyTrait for Currency { + fn is_native(&self) -> bool { + match self { + Currency::NativeCurrency(_) => true, + Currency::Token(_) => false, + } + } + + fn address(&self) -> String { + match self { + Currency::NativeCurrency(native_currency) => native_currency.address(), + Currency::Token(token) => token.address(), + } + } +} + +impl BaseCurrency for Currency { + fn chain_id(&self) -> u32 { + match self { + Currency::NativeCurrency(native_currency) => native_currency.chain_id(), + Currency::Token(token) => token.chain_id(), + } + } + + fn decimals(&self) -> u32 { + match self { + Currency::NativeCurrency(native_currency) => native_currency.decimals(), + Currency::Token(token) => token.decimals(), + } + } + + fn symbol(&self) -> Option { + match self { + Currency::NativeCurrency(native_currency) => native_currency.symbol(), + Currency::Token(token) => token.symbol(), + } + } + + fn name(&self) -> Option { + match self { + Currency::NativeCurrency(native_currency) => native_currency.name(), + Currency::Token(token) => token.name(), + } + } + + fn equals(&self, other: &impl CurrencyTrait) -> bool { + match self { + Currency::NativeCurrency(native_currency) => native_currency.equals(other), + Currency::Token(token) => token.equals(other), + } + } + + fn wrapped(&self) -> Token { + match self { + Currency::NativeCurrency(native_currency) => native_currency.wrapped(), + Currency::Token(token) => token.clone(), + } + } +} diff --git a/src/entities/ether.rs b/src/entities/ether.rs index 6745787..5f8fa57 100644 --- a/src/entities/ether.rs +++ b/src/entities/ether.rs @@ -1,4 +1,5 @@ -use super::{base_currency::BaseCurrency, token::Token}; +use super::{base_currency::BaseCurrency, currency::CurrencyTrait, token::Token}; +use crate::entities::weth9::WETH9; use lazy_static::lazy_static; use std::{collections::HashMap, sync::Mutex}; @@ -9,35 +10,32 @@ lazy_static! { /// Ether is the main usage of a 'native' currency, i.e. for Ethereum mainnet and all testnets #[derive(Clone, PartialEq)] pub struct Ether { - base_currency: BaseCurrency, - wrapped: Token, + pub chain_id: u32, + pub decimals: u32, + pub symbol: Option, + pub name: Option, +} + +impl CurrencyTrait for Ether { + fn is_native(&self) -> bool { + true + } + + fn address(&self) -> String { + self.wrapped().address() + } } impl Ether { pub fn new(chain_id: u32) -> Self { Self { - base_currency: BaseCurrency::new( - chain_id, - 18, - Some("Ether".to_string()), - Some("ETH".to_string()), - ), - wrapped: Token::new( - chain_id, - "0x".to_string(), - 18, - Some("WETH".to_string()), - Some("Wrapped Ether".to_string()), - None, - None, - ), + chain_id, + decimals: 18, + symbol: Some("ETH".to_string()), + name: Some("Ether".to_string()), } } - pub fn wrapped(&self) -> &Token { - &self.wrapped - } - pub fn on_chain(chain_id: u32) -> Self { let mut cache = ETHER_CACHE.lock().unwrap(); match cache.get(&chain_id) { @@ -49,15 +47,44 @@ impl Ether { } } } +} + +impl BaseCurrency for Ether { + fn chain_id(&self) -> u32 { + self.chain_id + } + + fn decimals(&self) -> u32 { + self.decimals + } + + fn symbol(&self) -> Option { + self.symbol.clone() + } + + fn name(&self) -> Option { + self.name.clone() + } + + fn equals(&self, other: &impl CurrencyTrait) -> bool { + match other.is_native() { + true => self.chain_id() == other.chain_id(), + _ => false, + } + } - pub fn equals(&self, other: &BaseCurrency) -> bool { - other.is_native && other.chain_id == self.base_currency.chain_id + fn wrapped(&self) -> Token { + match WETH9::default().get(self.chain_id()) { + Some(weth9) => weth9.clone(), + None => panic!("WRAPPED"), + } } } #[cfg(test)] mod tests { use super::*; + use crate::entities::currency::Currency; #[test] fn test_static_constructor_uses_cache() { @@ -71,11 +98,11 @@ mod tests { #[test] fn test_equals_returns_false_for_different_chains() { - assert!(!Ether::on_chain(1).equals(&Ether::on_chain(2).base_currency)); + assert!(!Ether::on_chain(1).equals(&Currency::NativeCurrency(Ether::on_chain(2)))); } #[test] fn test_equals_returns_true_for_same_chains() { - assert!(Ether::on_chain(1).equals(&Ether::on_chain(1).base_currency)); + assert!(Ether::on_chain(1).equals(&Currency::NativeCurrency(Ether::on_chain(1)))); } } diff --git a/src/entities/fractions/currency_amount.rs b/src/entities/fractions/currency_amount.rs index 1f3de4c..43e44c3 100644 --- a/src/entities/fractions/currency_amount.rs +++ b/src/entities/fractions/currency_amount.rs @@ -1,104 +1,319 @@ -use crate::entities::{ - currency::Currency, - fractions::fraction::{Fraction, Rounding}, +use crate::{ + constants::{Rounding, MAX_UINT256}, + entities::{ + base_currency::BaseCurrency, + currency::CurrencyTrait, + fractions::fraction::{Fraction, FractionTrait}, + token::Token, + }, }; -use num_bigint::{BigInt, BigUint}; -use num_traits::ToPrimitive; +use num_bigint::{BigInt, BigUint, ToBigInt}; +use num_integer::Integer; +use rust_decimal::Decimal; +use std::{ops::Div, str::FromStr}; #[derive(Clone, PartialEq)] -pub struct CurrencyAmount { - pub currency: Currency, - pub decimal_scale: Option, - pub fraction: Option, +pub struct CurrencyAmount { + numerator: BigInt, + denominator: BigInt, + pub meta: CurrencyMeta, } -impl CurrencyAmount { - pub fn new( - currency: Currency, - decimal_scale: Option, - fraction: Option, - ) -> Self { +#[derive(Clone, PartialEq)] +pub struct CurrencyMeta { + pub currency: T, + pub decimal_scale: BigUint, +} + +impl CurrencyAmount { + fn new(currency: T, numerator: impl Into, denominator: impl Into) -> Self { + let numerator = numerator.into(); + let denominator = denominator.into(); + assert!(numerator.div_floor(&denominator).le(&MAX_UINT256), "AMOUNT"); + let exponent = currency.decimals(); Self { - currency, - decimal_scale, - fraction, + numerator, + denominator, + meta: CurrencyMeta { + currency, + decimal_scale: BigUint::from(10u64).pow(exponent), + }, } } - pub fn from_raw_amount(currency: Currency, raw_amount: BigInt) -> CurrencyAmount { - let decimal_scale = BigUint::from(10_u32.pow(18_u32)); - let fraction = Fraction { - numerator: raw_amount.clone(), - denominator: BigInt::from(1), - }; + /// Returns a new currency amount instance from the unitless amount of token, i.e. the raw amount + /// + /// # Arguments + /// + /// * `currency`: the currency in the amount + /// * `raw_amount`: the raw token or ether amount + /// + /// returns: CurrencyAmount + /// + pub fn from_raw_amount(currency: T, raw_amount: impl Into) -> CurrencyAmount { + Self::new(currency, raw_amount, 1) + } + + /// Construct a currency amount with a denominator that is not equal to 1 + /// + /// # Arguments + /// + /// * `currency`: the currency + /// * `numerator`: the numerator of the fractional token amount + /// * `denominator`: the denominator of the fractional token amount + /// + /// returns: CurrencyAmount + /// + pub fn from_fractional_amount( + currency: T, + numerator: impl Into, + denominator: impl Into, + ) -> CurrencyAmount { + Self::new(currency, numerator, denominator) + } + + pub fn multiply(&self, other: &impl FractionTrait) -> Self { + let multiplied = self.as_fraction().multiply(&other.as_fraction()); + Self::from_fractional_amount( + self.meta.currency.clone(), + multiplied.numerator().clone(), + multiplied.denominator().clone(), + ) + } + + pub fn divide(&self, other: &impl FractionTrait) -> Self { + let divided = self.as_fraction().divide(&other.as_fraction()); + Self::from_fractional_amount( + self.meta.currency.clone(), + divided.numerator().clone(), + divided.denominator().clone(), + ) + } + + pub fn to_exact(&self) -> String { + Decimal::from_str(&self.quotient().to_str_radix(10)) + .unwrap() + .div(Decimal::from_str(&self.meta.decimal_scale.to_str_radix(10)).unwrap()) + .to_string() + } +} + +impl CurrencyAmount { + pub fn wrapped(&self) -> CurrencyAmount { + match &self.meta.currency.is_native() { + true => Self::from_fractional_amount( + self.meta.currency.wrapped(), + self.numerator.clone(), + self.denominator.clone(), + ), + false => self.clone(), + } + } +} +impl FractionTrait> for CurrencyAmount { + fn new( + numerator: impl Into, + denominator: impl Into, + meta: CurrencyMeta, + ) -> Self { Self { - currency, - decimal_scale: Some(decimal_scale), - fraction: Some(fraction), + numerator: numerator.into(), + denominator: denominator.into(), + meta, } } - pub fn from_fractional_amount( - currency: Currency, - numerator: BigInt, - denominator: BigInt, - ) -> CurrencyAmount { - CurrencyAmount::new(currency, None, Some(Fraction::new(numerator, denominator))) + fn meta(&self) -> CurrencyMeta { + self.meta.clone() } - pub fn add(&self, other: CurrencyAmount) -> CurrencyAmount { - assert!(self.currency == other.currency, "CURRENCY SHOULD EQUAL"); - let add = Fraction::add(&self.fraction.clone().unwrap(), &other.fraction.unwrap()); - CurrencyAmount::from_fractional_amount(other.currency, add.numerator, add.denominator) + fn numerator(&self) -> &BigInt { + &self.numerator } - pub fn subtract(&self, other: CurrencyAmount) -> CurrencyAmount { - assert!(self.currency == other.currency, "CURRENCY SHOULD EQUAL"); - let sub = Fraction::subtract(&self.fraction.clone().unwrap(), &other.fraction.unwrap()); - CurrencyAmount::from_fractional_amount(other.currency, sub.numerator, sub.denominator) + fn denominator(&self) -> &BigInt { + &self.denominator } - pub fn multiply(&self, other: CurrencyAmount) -> CurrencyAmount { - let multiplied = - Fraction::multiply(&self.fraction.clone().unwrap(), &other.fraction.unwrap()); - CurrencyAmount::from_fractional_amount( - other.currency, - multiplied.numerator, - multiplied.denominator, + fn add(&self, other: &Self) -> Self { + assert!(self.meta.currency.equals(&other.meta.currency), "CURRENCY"); + let added = self.as_fraction().add(&other.as_fraction()); + Self::from_fractional_amount( + self.meta.currency.clone(), + added.numerator().clone(), + added.denominator().clone(), ) } - pub fn divide(&self, other: CurrencyAmount) -> CurrencyAmount { - let divided = Fraction::divide(&self.fraction.clone().unwrap(), &other.fraction.unwrap()); - CurrencyAmount::from_fractional_amount( - other.currency, - divided.numerator, - divided.denominator, + fn subtract(&self, other: &Self) -> Self { + assert!(self.meta.currency.equals(&other.meta.currency), "CURRENCY"); + let subtracted = self.as_fraction().subtract(&other.as_fraction()); + Self::from_fractional_amount( + self.meta.currency.clone(), + subtracted.numerator().clone(), + subtracted.denominator().clone(), ) } - pub fn to_significant(&self) -> String { - Fraction::to_significant(&self.fraction.clone().unwrap(), 6, Rounding::RoundUp) + fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + self.as_fraction() + .divide(&Fraction::new( + self.meta.decimal_scale.to_bigint().unwrap(), + 1, + (), + )) + .to_significant(significant_digits, rounding) } - pub fn to_fixed(&self, decimals: BigUint) -> String { - assert!(decimals <= self.decimal_scale.clone().unwrap(), "DECIMALS"); - Fraction::to_fixed( - &self.fraction.clone().unwrap(), - decimals.to_u32().unwrap(), - Rounding::RoundUp, - ) + fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + assert!(decimal_places <= self.meta.currency.decimals(), "DECIMALS"); + self.as_fraction() + .divide(&Fraction::new( + self.meta.decimal_scale.to_bigint().unwrap(), + 1, + (), + )) + .to_fixed(decimal_places, rounding) } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::entities::{ + currency::Currency, ether::Ether, fractions::percent::Percent, token::Token, + }; + use lazy_static::lazy_static; + + const ADDRESS_ONE: &str = "0x0000000000000000000000000000000000000001"; - //Implementation not done yet - pub fn to_exact() {} + lazy_static! { + static ref TOKEN18: Currency = Currency::Token(Token::new( + 1, + ADDRESS_ONE.to_string(), + 18, + None, + None, + None, + None, + )); + static ref TOKEN0: Currency = Currency::Token(Token::new( + 1, + ADDRESS_ONE.to_string(), + 0, + None, + None, + None, + None, + )); + } + + #[test] + fn test_constructor() { + let amount = CurrencyAmount::from_raw_amount(TOKEN18.clone(), 100); + assert_eq!(amount.quotient(), 100.into()); + } - // pub fn wrapped(&self) -> CurrencyAmount { - // if Token::is_token() {//don't really understand this part, but is_token will always return true anyway - // self.clone() - // } else { - // let wrapped = Token::wrapped(Currency::Token(Token)); - // } - // } + #[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)); + } + + #[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()); + assert!(amount + .meta + .currency + .equals(&Currency::NativeCurrency(ether.clone()))); + } + + #[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()); + } + + #[test] + #[should_panic(expected = "AMOUNT")] + fn test_token_amount_exceeds_max_uint256() { + let _ = CurrencyAmount::from_raw_amount(TOKEN18.clone(), MAX_UINT256.clone() + 1); + } + + #[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); + } + + #[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); + } + + #[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); + } + + #[test] + fn to_fixed_0_decimals() { + let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 123456); + assert_eq!(amount.to_fixed(0, Rounding::RoundDown), "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"); + } + + #[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"); + } + + #[test] + fn to_significant_0_decimals() { + let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 123456); + assert_eq!(amount.to_significant(4, Rounding::RoundDown), "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"); + } + + #[test] + fn to_exact_does_not_throw() { + let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 1000); + assert_eq!(amount.to_exact(), "1000"); + } + + #[test] + fn to_exact_0_decimals() { + let amount = CurrencyAmount::from_raw_amount(TOKEN0.clone(), 123456); + assert_eq!(amount.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"); + } } diff --git a/src/entities/fractions/fraction.rs b/src/entities/fractions/fraction.rs index 97850b5..c84525d 100644 --- a/src/entities/fractions/fraction.rs +++ b/src/entities/fractions/fraction.rs @@ -1,16 +1,13 @@ +use crate::constants::Rounding; use num_bigint::BigInt; +use num_integer::Integer; use rust_decimal::prelude::*; +use std::ops::Div; #[derive(Clone, PartialEq)] pub struct Fraction { - pub numerator: BigInt, - pub denominator: BigInt, -} - -pub enum Rounding { - RoundDown, - RoundHalfUp, - RoundUp, + numerator: BigInt, + denominator: BigInt, } fn to_rounding_strategy(rounding: Rounding) -> RoundingStrategy { @@ -21,103 +18,259 @@ fn to_rounding_strategy(rounding: Rounding) -> RoundingStrategy { } } -impl Fraction { - pub fn new(numerator: BigInt, denominator: BigInt) -> Self { - assert!(denominator != BigInt::from(0), "DENOMINATOR CAN'T BE ZERO"); - Self { - numerator, - denominator, - } - } +pub trait FractionTrait: Sized { + fn new(numerator: impl Into, denominator: impl Into, meta: M) -> Self; + + /// Additional metadata that can be attached to the fraction + fn meta(&self) -> M; - pub fn quotient(&self) -> BigInt { - &self.numerator / &self.denominator + fn numerator(&self) -> &BigInt; + + fn denominator(&self) -> &BigInt; + + /// performs floor division + fn quotient(&self) -> BigInt { + self.numerator().div_floor(self.denominator()) } - pub fn remainder(&self) -> Fraction { - Fraction::new( - &self.numerator % &self.denominator, - self.denominator.clone(), + /// remainder after floor division + fn remainder(&self) -> Self { + Self::new( + self.numerator() % self.denominator(), + self.denominator().clone(), + self.meta(), ) } - pub fn invert(&self) -> Self { - Fraction::new(self.numerator.clone(), self.denominator.clone()) + fn invert(&self) -> Self { + Self::new( + self.denominator().clone(), + self.numerator().clone(), + self.meta(), + ) } - pub fn add(&self, other: &Fraction) -> Fraction { - if self.denominator == other.denominator { - Fraction::new(&self.numerator + &other.numerator, self.denominator.clone()) + fn add(&self, other: &Self) -> Self { + if self.denominator() == other.denominator() { + Self::new( + self.numerator() + other.numerator(), + self.denominator().clone(), + self.meta(), + ) } else { - Fraction::new( - &self.numerator * &other.denominator + &other.numerator * &self.denominator, - &self.denominator * &other.denominator, + Self::new( + self.numerator() * other.denominator() + other.numerator() * self.denominator(), + self.denominator() * other.denominator(), + self.meta(), ) } } - pub fn subtract(&self, other: &Fraction) -> Fraction { - if self.denominator == other.denominator { - Fraction::new(&self.numerator - &other.numerator, self.denominator.clone()) + fn subtract(&self, other: &Self) -> Self { + if self.denominator() == other.denominator() { + Self::new( + self.numerator() - other.numerator(), + self.denominator().clone(), + self.meta(), + ) } else { - Fraction::new( - &self.numerator * &other.denominator - &other.numerator * &self.denominator, - &self.denominator * &other.denominator, + Self::new( + self.numerator() * other.denominator() - other.numerator() * self.denominator(), + self.denominator() * other.denominator(), + self.meta(), ) } } - pub fn less_than(&self, other: &Fraction) -> bool { - &self.numerator * &other.denominator < &other.numerator * &self.denominator + fn multiply(&self, other: &Self) -> Self { + Self::new( + self.numerator() * other.numerator(), + self.denominator() * other.denominator(), + self.meta(), + ) + } + + fn divide(&self, other: &Self) -> Self { + Self::new( + self.numerator() * other.denominator(), + self.denominator() * other.numerator(), + self.meta(), + ) } - pub fn equal_to(&self, other: &Fraction) -> bool { - &self.numerator * &other.denominator == &other.numerator * &self.denominator + fn less_than(&self, other: &Self) -> bool { + self.numerator() * other.denominator() < other.numerator() * self.denominator() } - pub fn greater_than(&self, other: &Fraction) -> bool { - &self.numerator * &other.denominator > &other.numerator * &self.denominator + fn equal_to(&self, other: &Self) -> bool { + self.numerator() * other.denominator() == other.numerator() * self.denominator() } - pub fn multiply(&self, other: &Fraction) -> Fraction { - Fraction::new( - &self.numerator * &other.numerator, - &self.denominator * &other.denominator, - ) + fn greater_than(&self, other: &Self) -> bool { + self.numerator() * other.denominator() > other.numerator() * self.denominator() } - pub fn divide(&self, other: &Fraction) -> Fraction { - Fraction::new( - &self.numerator * &other.denominator, - &self.denominator * &other.numerator, - ) + fn to_decimal(&self) -> Decimal { + Decimal::from_str(&self.numerator().to_str_radix(10)) + .unwrap() + .div(Decimal::from_str(&self.denominator().to_str_radix(10)).unwrap()) } - pub fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { assert!( significant_digits > 0, "Significant digits must be positive." ); let rounding_strategy = to_rounding_strategy(rounding); + let quotient = self + .to_decimal() + .round_sf_with_strategy(significant_digits, rounding_strategy); - let quotient = &self.numerator / &self.denominator; - let quotient = Decimal::from_str("ient.to_str_radix(10)).unwrap(); - let quotient = quotient.round_dp_with_strategy(significant_digits, rounding_strategy); - - quotient.to_string() + quotient.unwrap().normalize().to_string() } - pub fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { let rounding_strategy = to_rounding_strategy(rounding); + let quotient = self + .to_decimal() + .round_dp_with_strategy(decimal_places, rounding_strategy); + + format!("{:.1$}", quotient, decimal_places as usize) + } - let quotient = &self.numerator / &self.denominator; - let quotient = Decimal::from_str("ient.to_str_radix(10)).unwrap(); - let quotient = quotient.round_dp_with_strategy(decimal_places, rounding_strategy); + /// Helper method for converting any super class back to a fraction + fn as_fraction(&self) -> Fraction { + Fraction::new(self.numerator().clone(), self.denominator().clone(), ()) + } +} + +impl FractionTrait<()> for Fraction { + fn new(numerator: impl Into, denominator: impl Into, _: ()) -> Self { + let denominator = denominator.into(); + assert_ne!(denominator, 0.into(), "DENOMINATOR CAN'T BE ZERO"); + Self { + numerator: numerator.into(), + denominator, + } + } - quotient.to_string() + fn meta(&self) -> () { + () } - pub fn as_fraction(&self) -> Fraction { - Fraction::new(self.numerator.clone(), self.denominator.clone()) + + fn numerator(&self) -> &BigInt { + &self.numerator + } + + fn denominator(&self) -> &BigInt { + &self.denominator + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_quotient() { + assert_eq!(Fraction::new(8, 3, ()).quotient(), BigInt::from(2)); + assert_eq!(Fraction::new(12, 4, ()).quotient(), BigInt::from(3)); + assert_eq!(Fraction::new(16, 5, ()).quotient(), BigInt::from(3)); + } + + #[test] + fn test_remainder() { + assert!(Fraction::new(8, 3, ()) + .remainder() + .equal_to(&Fraction::new(2, 3, ()))); + assert!(Fraction::new(12, 4, ()) + .remainder() + .equal_to(&Fraction::new(0, 4, ()))); + assert!(Fraction::new(16, 5, ()) + .remainder() + .equal_to(&Fraction::new(1, 5, ()))); + } + + #[test] + fn test_invert() { + let fraction = Fraction::new(5, 10, ()).invert(); + assert_eq!(fraction.numerator, BigInt::from(10)); + assert_eq!(fraction.denominator, BigInt::from(5)); + } + + #[test] + fn test_add() { + assert!(Fraction::new(1, 10, ()) + .add(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(52, 120, ()))); + assert!(Fraction::new(1, 5, ()) + .add(&Fraction::new(2, 5, ())) + .equal_to(&Fraction::new(3, 5, ()))); + } + + #[test] + fn test_subtract() { + assert!(Fraction::new(1, 10, ()) + .subtract(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(-28, 120, ()))); + assert!(Fraction::new(3, 5, ()) + .subtract(&Fraction::new(2, 5, ())) + .equal_to(&Fraction::new(1, 5, ()))); + } + + #[test] + fn test_less_than() { + assert!(Fraction::new(1, 10, ()).less_than(&Fraction::new(4, 12, ()))); + assert!(!Fraction::new(1, 3, ()).less_than(&Fraction::new(4, 12, ()))); + assert!(!Fraction::new(5, 12, ()).less_than(&Fraction::new(4, 12, ()))); + } + + #[test] + fn test_equal_to() { + assert!(!Fraction::new(1, 10, ()).equal_to(&Fraction::new(4, 12, ()))); + assert!(Fraction::new(1, 3, ()).equal_to(&Fraction::new(4, 12, ()))); + assert!(!Fraction::new(5, 12, ()).equal_to(&Fraction::new(4, 12, ()))); + } + + #[test] + fn test_greater_than() { + assert!(!Fraction::new(1, 10, ()).greater_than(&Fraction::new(4, 12, ()))); + assert!(!Fraction::new(1, 3, ()).greater_than(&Fraction::new(4, 12, ()))); + assert!(Fraction::new(5, 12, ()).greater_than(&Fraction::new(4, 12, ()))); + } + + #[test] + fn test_multiply() { + assert!(Fraction::new(1, 10, ()) + .multiply(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(4, 120, ()))); + assert!(Fraction::new(1, 3, ()) + .multiply(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(4, 36, ()))); + assert!(Fraction::new(5, 12, ()) + .multiply(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(20, 144, ()))); + } + + #[test] + fn test_divide() { + assert!(Fraction::new(1, 10, ()) + .divide(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(12, 40, ()))); + assert!(Fraction::new(1, 3, ()) + .divide(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(12, 12, ()))); + assert!(Fraction::new(5, 12, ()) + .divide(&Fraction::new(4, 12, ())) + .equal_to(&Fraction::new(60, 48, ()))); + } + + #[test] + fn test_as_faction() { + let f = Fraction::new(1, 2, ()); + // returns an equivalent but not the same reference fraction + assert!(f.as_fraction().equal_to(&f)); + assert_ne!(&f as *const _, &f.as_fraction() as *const _); } } diff --git a/src/entities/fractions/percent.rs b/src/entities/fractions/percent.rs index 612e3e1..2d08104 100644 --- a/src/entities/fractions/percent.rs +++ b/src/entities/fractions/percent.rs @@ -1,49 +1,114 @@ -use super::fraction::{Fraction, Rounding}; +use super::fraction::{Fraction, FractionTrait}; +use crate::constants::Rounding; +use lazy_static::lazy_static; use num_bigint::BigInt; +lazy_static! { + static ref ONE_HUNDRED: Fraction = Fraction::new(100, 1, ()); +} + +#[derive(Clone, PartialEq)] pub struct Percent { - fraction: Fraction, + numerator: BigInt, + denominator: BigInt, } impl Percent { + /// This boolean prevents a fraction from being interpreted as a Percent pub const IS_PERCENT: bool = true; +} - pub fn new(fraction: Fraction) -> Self { - Self { fraction } +impl FractionTrait<()> for Percent { + fn new(numerator: impl Into, denominator: impl Into, _: ()) -> Self { + Self { + numerator: numerator.into(), + denominator: denominator.into(), + } } - pub fn add(&self, other: &Fraction) -> Percent { - let fraction = self.fraction.add(other); - Percent::new(fraction) + fn meta(&self) -> () { + () } - pub fn sub(&self, other: &Fraction) -> Percent { - let fraction = self.fraction.subtract(other); - Percent::new(fraction) + fn numerator(&self) -> &BigInt { + &self.numerator } - pub fn multiply(&self, other: &Fraction) -> Percent { - let fraction = self.fraction.multiply(other); - Percent::new(fraction) - } - pub fn divide(&self, other: &Fraction) -> Percent { - let fraction = self.fraction.divide(other); - Percent::new(fraction) + fn denominator(&self) -> &BigInt { + &self.denominator } - pub fn to_significant(&self, other: &Fraction) -> String { - let hundred = Fraction::new(BigInt::from(100), BigInt::from(1)); - let mult = Fraction::multiply(&hundred, other); - Fraction::to_significant(&mult, 5, Rounding::RoundUp) + fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + self.as_fraction() + .multiply(&ONE_HUNDRED) + .to_significant(significant_digits, rounding) } - pub fn to_fixed(&self, other: &Fraction) -> String { - let hundred = Fraction::new(BigInt::from(100), BigInt::from(1)); - let mult = Fraction::multiply(&hundred, other); - Fraction::to_fixed(&mult, 2, Rounding::RoundUp) + fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + self.as_fraction() + .multiply(&ONE_HUNDRED) + .to_fixed(decimal_places, rounding) } } -pub fn to_percent(fraction: Fraction) -> Percent { - Percent::new(fraction) +#[cfg(test)] +mod tests { + use super::*; + use crate::constants::Rounding; + + #[test] + fn test_add() { + assert!(Percent::new(1, 100, ()) + .add(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(3, 100, ()))); + assert!(Percent::new(1, 25, ()) + .add(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(150, 2500, ()))); + } + + #[test] + fn test_subtract() { + assert!(Percent::new(1, 100, ()) + .subtract(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(-1, 100, ()))); + assert!(Percent::new(1, 25, ()) + .subtract(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(50, 2500, ()))); + } + + #[test] + fn test_multiply() { + assert!(Percent::new(1, 100, ()) + .multiply(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(2, 10000, ()))); + assert!(Percent::new(1, 25, ()) + .multiply(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(2, 2500, ()))); + } + + #[test] + fn test_divide() { + assert!(Percent::new(1, 100, ()) + .divide(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(100, 200, ()))); + assert!(Percent::new(1, 25, ()) + .divide(&Percent::new(2, 100, ())) + .equal_to(&Percent::new(100, 50, ()))); + } + + #[test] + fn test_to_significant() { + assert_eq!( + Percent::new(154, 10000, ()).to_significant(3, Rounding::RoundHalfUp), + "1.54".to_string() + ); + } + + #[test] + fn test_to_fixed() { + assert_eq!( + Percent::new(154, 10000, ()).to_fixed(2, Rounding::RoundHalfUp), + "1.54".to_string() + ); + } } diff --git a/src/entities/fractions/price.rs b/src/entities/fractions/price.rs index 74e8836..3abde75 100644 --- a/src/entities/fractions/price.rs +++ b/src/entities/fractions/price.rs @@ -1,65 +1,281 @@ -// use super::{ -// currency_amount::CurrencyAmount, -// fraction::{Fraction, Rounding}, -// }; -// use num_bigint::BigInt; - -// pub trait Currency { -// fn decimals(&self) -> u32; -// } - -// #[derive(Clone, PartialEq)] - -// pub struct Price { -// pub base_currency: TBase, -// pub quote_currency: TQuote, -// pub numerator: BigInt, -// pub denominator: BigInt, -// pub scalar: Fraction, -// } - -// impl Price { -// pub fn new( -// base_currency: TBase, -// quote_currency: TQuote, -// numerator: BigInt, -// denominator: BigInt, -// ) -> Self { -// let scalar = Fraction::new( -// BigInt::from(10).pow(base_currency.decimals() as u32), -// BigInt::from(10).pow(quote_currency.decimals() as u32), -// ); - -// Self { -// base_currency, -// quote_currency, -// numerator, -// denominator, -// scalar, -// } -// } - -// /** -// * Flip the price, switching the base and quote currency -// */ -// pub fn invert(self) -> Price { -// Price::new( -// self.base_currency, -// self.quote_currency, -// self.numerator, -// self.denominator, -// ) -// } - -// /** -// * Multiply the price by another price, returning a new price. The other price must have the same base currency as this price's quote currency -// * @param other the other price -// */ -// pub fn multiply(&self, other: Price) -> Price { -// assert!(self.quote_currency == other.base_currency, "TOKEN"); -// let fract = Fraction::multiply(&self, other); -// Price::new(self.base_currency.clone(), other.quote_currency.clone(), fract.numerator, fract.denominator) -// } - -// // Methods toSignificant and toFixed would be implemented here -// } +use crate::{ + constants::Rounding, + entities::{ + currency::CurrencyTrait, + fractions::{ + currency_amount::CurrencyAmount, + fraction::{Fraction, FractionTrait}, + }, + }, +}; +use num_bigint::BigInt; + +#[derive(Clone)] +pub struct Price +where + TBase: CurrencyTrait, + TQuote: CurrencyTrait, +{ + numerator: BigInt, + denominator: BigInt, + pub meta: PriceMeta, +} + +#[derive(Clone)] +pub struct PriceMeta +where + TBase: CurrencyTrait, + TQuote: CurrencyTrait, +{ + pub base_currency: TBase, + pub quote_currency: TQuote, + pub scalar: Fraction, +} + +impl FractionTrait> for Price +where + TBase: CurrencyTrait, + TQuote: CurrencyTrait, +{ + fn new( + numerator: impl Into, + denominator: impl Into, + meta: PriceMeta, + ) -> Self { + Self { + numerator: numerator.into(), + denominator: denominator.into(), + meta, + } + } + + fn meta(&self) -> PriceMeta { + self.meta.clone() + } + + fn numerator(&self) -> &BigInt { + &self.numerator + } + + fn denominator(&self) -> &BigInt { + &self.denominator + } + + fn to_significant(&self, significant_digits: u32, rounding: Rounding) -> String { + self.adjusted_for_decimals() + .to_significant(significant_digits, rounding) + } + + fn to_fixed(&self, decimal_places: u32, rounding: Rounding) -> String { + self.adjusted_for_decimals() + .to_fixed(decimal_places, rounding) + } +} + +impl Price +where + TBase: CurrencyTrait, + TQuote: CurrencyTrait, +{ + pub fn new( + base_currency: TBase, + quote_currency: TQuote, + denominator: impl Into, + numerator: impl Into, + ) -> Self { + let scalar = Fraction::new( + BigInt::from(10).pow(base_currency.decimals()), + BigInt::from(10).pow(quote_currency.decimals()), + (), + ); + Self { + numerator: numerator.into(), + denominator: denominator.into(), + meta: PriceMeta { + base_currency, + quote_currency, + scalar, + }, + } + } + + pub fn from_currency_amounts( + base_amount: CurrencyAmount, + quote_amount: CurrencyAmount, + ) -> Self { + let res = quote_amount.divide(&base_amount); + Self::new( + base_amount.meta.currency, + quote_amount.meta.currency, + res.denominator().clone(), + res.numerator().clone(), + ) + } + + /// Flip the price, switching the base and quote currency + pub fn invert(self) -> Price { + Price::new( + self.meta.quote_currency, + self.meta.base_currency, + self.numerator, + self.denominator, + ) + } + + /// Multiply the price by another price, returning a new price. The other price must have the same base currency as this price's quote currency + /// + /// # Arguments + /// + /// * `other`: the other price + /// + /// returns: Price + /// + pub fn multiply( + &self, + other: Price, + ) -> Price { + assert!( + self.meta.quote_currency.equals(&other.meta.base_currency), + "TOKEN" + ); + let fraction = self.as_fraction().multiply(&other.as_fraction()); + 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 + /// + /// # Arguments + /// + /// * `currency_amount`: the amount of base currency to quote against the price + /// + /// returns: CurrencyAmount + /// + pub fn quote(&self, currency_amount: CurrencyAmount) -> CurrencyAmount { + assert!( + currency_amount + .meta + .currency + .equals(&self.meta.base_currency), + "TOKEN" + ); + let fraction = self.as_fraction().multiply(¤cy_amount.as_fraction()); + CurrencyAmount::from_fractional_amount( + self.meta.quote_currency.clone(), + fraction.numerator().clone(), + fraction.denominator().clone(), + ) + } + + /// Get the value scaled by decimals for formatting + pub fn adjusted_for_decimals(&self) -> Fraction { + self.as_fraction().multiply(&self.meta.scalar) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::entities::{base_currency::BaseCurrency, currency::Currency, token::Token}; + use lazy_static::lazy_static; + + const ADDRESS_ZERO: &str = "0x0000000000000000000000000000000000000000"; + const ADDRESS_ONE: &str = "0x0000000000000000000000000000000000000001"; + + lazy_static! { + static ref TOKEN0: Currency = Currency::Token(Token::new( + 1, + ADDRESS_ZERO.to_string(), + 18, + None, + None, + None, + None, + )); + static ref TOKEN0_6: Currency = Currency::Token(Token::new( + 1, + ADDRESS_ZERO.to_string(), + 6, + None, + None, + None, + None, + )); + static ref TOKEN1: Currency = Currency::Token(Token::new( + 1, + ADDRESS_ONE.to_string(), + 18, + None, + None, + None, + None, + )); + } + + #[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())); + } + + #[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), + ); + 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())); + } + + #[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)) + .equal_to(&CurrencyAmount::from_raw_amount(TOKEN1.clone(), 50))); + } + + #[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"); + } + + #[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"); + } + + #[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), + "0.000000000003707" + ); + } + + #[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), + "0.0000000000002697" + ); + } + + #[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"); + } +} diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 6e0c6df..fbd5c8a 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -2,6 +2,5 @@ pub mod base_currency; pub mod currency; pub mod ether; pub mod fractions; -pub mod native_currency; pub mod token; pub mod weth9; diff --git a/src/entities/native_currency.rs b/src/entities/native_currency.rs deleted file mode 100644 index e4aa7ef..0000000 --- a/src/entities/native_currency.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(Clone, PartialEq)] -pub struct NativeCurrency { - pub is_native: bool, - pub is_token: bool, -} diff --git a/src/entities/token.rs b/src/entities/token.rs index 878e7e3..4580023 100644 --- a/src/entities/token.rs +++ b/src/entities/token.rs @@ -1,15 +1,69 @@ -use super::base_currency::BaseCurrency; +use super::{base_currency::BaseCurrency, currency::CurrencyTrait}; use num_bigint::BigUint; /// Represents an ERC20 token with a unique address and some metadata. #[derive(Clone, PartialEq)] pub struct Token { - pub base_currency: BaseCurrency, + pub chain_id: u32, pub address: String, + pub decimals: u32, + pub symbol: Option, + pub name: Option, pub buy_fee_bps: Option, pub sell_fee_bps: Option, } +impl CurrencyTrait for Token { + fn is_native(&self) -> bool { + false + } + + fn address(&self) -> String { + self.address.to_string() + } +} + +impl BaseCurrency for Token { + fn chain_id(&self) -> u32 { + self.chain_id + } + + fn decimals(&self) -> u32 { + self.decimals + } + + fn symbol(&self) -> Option { + self.symbol.clone() + } + + fn name(&self) -> Option { + self.name.clone() + } + + /// Returns true if the two tokens are equivalent, i.e. have the same chainId and address. + /// + /// # Arguments + /// + /// * `other`: other token to compare + /// + /// returns: bool + /// + fn equals(&self, other: &impl CurrencyTrait) -> bool { + match other.is_native() { + false => { + self.chain_id == other.chain_id() + && self.address.to_lowercase() == other.address().to_lowercase() + } + _ => false, + } + } + + /// Return this token, which does not need to be wrapped + fn wrapped(&self) -> Token { + self.clone() + } +} + impl Token { pub fn new( chain_id: u32, @@ -23,45 +77,25 @@ impl Token { assert!(chain_id > 0, "CHAIN_ID"); assert!(decimals < 255, "DECIMALS"); Self { - base_currency: BaseCurrency::new(chain_id, decimals, symbol, name), + chain_id, address, + decimals, + symbol, + name, buy_fee_bps, sell_fee_bps, } } - pub fn is_native(&self) -> bool { - false - } - - pub fn is_token(&self) -> bool { - true - } - - /// Returns true if the two tokens are equivalent, i.e. have the same chainId and address. - /// - /// # Arguments - /// - /// * `other` - The other token to compare. - /// - pub fn equals(&self, other: &Token) -> bool { - other.is_token() - && self.base_currency.chain_id == other.base_currency.chain_id - && self.address.to_lowercase() == other.address.to_lowercase() - } - /// Returns true if the address of this token sorts before the address of the other token. /// Panics if the tokens have the same address or if the tokens are on different chains. /// /// # Arguments /// - /// * `other` - The other token to compare. + /// * `other`: other token to compare /// pub fn sorts_before(&self, other: &Token) -> bool { - assert_eq!( - self.base_currency.chain_id, other.base_currency.chain_id, - "CHAIN_IDS" - ); + assert_eq!(self.chain_id, other.chain_id, "CHAIN_IDS"); assert_ne!( self.address.to_lowercase(), other.address.to_lowercase(), @@ -69,16 +103,14 @@ impl Token { ); self.address.to_lowercase() < other.address.to_lowercase() } - - pub fn wrapped(&self) -> Token { - self.clone() - } } #[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::Token; + use super::*; + const ADDRESS_ONE: &str = "0x0000000000000000000000000000000000000001"; const ADDRESS_TWO: &str = "0x0000000000000000000000000000000000000002"; const DAI_MAINNET: &str = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; @@ -146,7 +178,7 @@ mod tests { ); assert!( - token.equals(&token_1), + token.equals(&Currency::Token(token_1)), "SHOULD_FAILS_EVEN_THOUGH_CHAIN_ID_IS_DIFFERENT" ); } @@ -173,7 +205,10 @@ mod tests { None, ); - assert!(token.equals(&token_1), "true even if names differ"); + assert!( + token.equals(&Currency::Token(token_1)), + "true even if names differ" + ); } #[test] @@ -198,7 +233,10 @@ mod tests { None, ); - assert!(token.equals(&token_1), "true even if symbols differ"); + assert!( + token.equals(&Currency::Token(token_1)), + "true even if symbols differ" + ); } #[test] @@ -225,7 +263,7 @@ mod tests { ); assert!( - token.equals(&token_1), + token.equals(&Currency::Token(token_1)), "SHOULD_FAILS_EVEN_THOUGH_ADDRESS_IS_DIFFERENT" ); } @@ -252,6 +290,10 @@ mod tests { None, ); - assert_eq!(token.equals(&token_1), token_1.equals(&token), "SHOULD_FAILS_EVEN_THOUGH_ADDRESS_IS_DIFFERENT, SHOULD ONLY REVERT FOR DIFFERENT CHAIN_ID"); + 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" + ); } } diff --git a/src/utils/sqrt.rs b/src/utils/sqrt.rs index 0fcfc9f..d651d28 100644 --- a/src/utils/sqrt.rs +++ b/src/utils/sqrt.rs @@ -5,11 +5,11 @@ use num_traits::{ToPrimitive, Zero}; /// /// # Arguments /// -/// * `value`: The value for which to compute the square root, rounded down +/// * `value`: the value for which to compute the square root, rounded down /// /// returns: BigInt /// -fn sqrt(value: &BigInt) -> BigInt { +pub fn sqrt(value: &BigInt) -> BigInt { assert!(*value >= Zero::zero(), "NEGATIVE"); // If the value is less than or equal to MAX_SAFE_INTEGER,