From 40fbdff05a665b427b424b9d7432f7f5c37da8d2 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 14 Sep 2021 10:29:31 +0200 Subject: [PATCH] Coin and denom tests --- tauri-wallet/src-tauri/src/coin.rs | 204 ++++++++++++++++++++++++++--- 1 file changed, 189 insertions(+), 15 deletions(-) diff --git a/tauri-wallet/src-tauri/src/coin.rs b/tauri-wallet/src-tauri/src/coin.rs index 274ae32e79..e77ae07da4 100644 --- a/tauri-wallet/src-tauri/src/coin.rs +++ b/tauri-wallet/src-tauri/src/coin.rs @@ -9,23 +9,25 @@ use cosmwasm_std::Uint128; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; -use std::ops::Add; +use std::ops::{Add, Sub}; use std::str::FromStr; use ts_rs::TS; use validator_client::nymd::GasPrice; use crate::format_err; -#[derive(TS, Serialize, Deserialize, Clone)] +#[derive(TS, Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum Denom { Major, Minor, } +const MINOR_IN_MAJOR: f64 = 1_000_000.; + impl fmt::Display for Denom { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - Denom::Major => f.write_str(&DENOM[1..].to_uppercase()), + Denom::Major => f.write_str(&DENOM[1..]), Denom::Minor => f.write_str(DENOM), } } @@ -49,7 +51,7 @@ impl FromStr for Denom { } } -#[derive(TS, Serialize, Deserialize, Clone)] +#[derive(TS, Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct Coin { amount: String, denom: Denom, @@ -93,12 +95,55 @@ impl Add for Coin { } } +// Allows adding minor and major denominations, output will have the LHS denom. +impl Sub for Coin { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + let denom = self.denom.clone(); + let lhs = self.to_minor(); + let rhs = rhs.to_minor(); + let lhs_amount = lhs.amount.parse::().unwrap(); + let rhs_amount = rhs.amount.parse::().unwrap(); + let amount = lhs_amount - rhs_amount; + let coin = Coin { + amount: amount.to_string(), + denom: Denom::Minor, + }; + match denom { + Denom::Major => coin.to_major(), + Denom::Minor => coin, + } + } + } + impl Coin { + pub fn major(amount: T) -> Coin { + Coin { + amount: amount.to_string(), + denom: Denom::Major, + } + } + + pub fn minor(amount: T) -> Coin { + Coin { + amount: amount.to_string(), + denom: Denom::Minor, + } + } + + pub fn new(amount: T, denom: &Denom) -> Coin { + Coin { + amount: amount.to_string(), + denom: denom.clone(), + } + } + pub fn to_major(&self) -> Coin { match self.denom { Denom::Major => self.clone(), Denom::Minor => Coin { - amount: (self.amount.parse::().unwrap() / 1_000_000.).to_string(), + amount: (self.amount.parse::().unwrap() / MINOR_IN_MAJOR).to_string(), denom: Denom::Major, }, } @@ -108,7 +153,7 @@ impl Coin { match self.denom { Denom::Minor => self.clone(), Denom::Major => Coin { - amount: (self.amount.parse::().unwrap() * 1_000_000.).to_string(), + amount: (self.amount.parse::().unwrap() * MINOR_IN_MAJOR).to_string(), denom: Denom::Minor, }, } @@ -121,20 +166,12 @@ impl Coin { pub fn denom(&self) -> Denom { self.denom.clone() } - - pub fn new(amount: &str, denom: &Denom) -> Coin { - Coin { - amount: amount.to_string(), - denom: denom.clone(), - } - } } impl TryFrom for CosmWasmCoin { type Error = String; fn try_from(coin: Coin) -> Result { - let coin = coin.to_minor(); Ok(CosmWasmCoin::new( Uint128::try_from(coin.amount.as_str()).unwrap().u128(), coin.denom.to_string(), @@ -146,7 +183,6 @@ impl TryFrom for CosmosCoin { type Error = String; fn try_from(coin: Coin) -> Result { - let coin = coin.to_minor(); match Decimal::from_str(&coin.amount) { Ok(d) => Ok(CosmosCoin { amount: d, @@ -174,3 +210,141 @@ impl From for Coin { } } } + +#[cfg(test)] +mod test { + use crate::{Coin, Denom}; + use cosmos_sdk::Coin as CosmosCoin; + use cosmos_sdk::Decimal; + use cosmos_sdk::Denom as CosmosDenom; + use cosmwasm_std::Coin as CosmWasmCoin; + use serde_json::json; + use std::convert::{TryFrom, TryInto}; + use std::str::FromStr; + + #[test] + fn json_to_coin() { + let minor = json!({ + "amount": "1", + "denom": "Minor" + }); + + let major = json!({ + "amount": "1", + "denom": "Major" + }); + + let test_minor_coin = Coin::minor("1"); + let test_major_coin = Coin::major("1"); + + let minor_coin = serde_json::from_value::(minor).unwrap(); + let major_coin = serde_json::from_value::(major).unwrap(); + + assert_eq!(minor_coin, test_minor_coin); + assert_eq!(major_coin, test_major_coin); + } + + #[test] + fn denom_conversions() { + let minor = Coin::minor("1"); + let major = minor.to_major(); + + assert_eq!(major, Coin::major("0.000001")); + + let minor = major.to_minor(); + assert_eq!(minor, Coin::minor("1")); + } + + fn amounts() -> Vec<&'static str> { + vec![ + "1", + "10", + "100", + "1000", + "10000", + "100000", + "10000000", + "100000000", + "1000000000", + "10000000000", + "100000000000", + "1000000000000", + "10000000000000", + "100000000000000", + "1000000000000000", + "10000000000000000", + "100000000000000000", + "1000000000000000000" + ] + } + + #[test] + fn coin_to_cosmoswasm() { + for amount in amounts() { + let coin: Coin = Coin::minor(amount).into(); + let cosmoswasm_coin: CosmWasmCoin = coin.try_into().unwrap(); + assert_eq!( + cosmoswasm_coin, + CosmWasmCoin::new(amount.parse::().unwrap(), Denom::Minor.to_string()) + ); + assert_eq!( + Coin::try_from(cosmoswasm_coin).unwrap(), + Coin::minor(amount) + ); + + let coin: Coin = Coin::major(amount).into(); + let cosmoswasm_coin: CosmWasmCoin = coin.try_into().unwrap(); + assert_eq!( + cosmoswasm_coin, + CosmWasmCoin::new(amount.parse::().unwrap(), Denom::Major.to_string()) + ); + assert_eq!( + Coin::try_from(cosmoswasm_coin).unwrap(), + Coin::major(amount) + ); + } + } + + #[test] + fn coin_to_cosmos() { + for amount in amounts() { + let coin: Coin = Coin::minor(amount).into(); + let cosmos_coin: CosmosCoin = coin.try_into().unwrap(); + assert_eq!( + cosmos_coin, + CosmosCoin { + amount: Decimal::from_str(amount).unwrap(), + denom: CosmosDenom::from_str(&Denom::Minor.to_string()).unwrap() + } + ); + assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::minor(amount)); + + let coin: Coin = Coin::major(amount).into(); + let cosmos_coin: CosmosCoin = coin.try_into().unwrap(); + assert_eq!( + cosmos_coin, + CosmosCoin { + amount: Decimal::from_str(amount).unwrap(), + denom: CosmosDenom::from_str(&Denom::Major.to_string()).unwrap() + } + ); + assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::major(amount)); + } + } + + #[test] + fn test_add() { + assert_eq!(Coin::minor("1") + Coin::minor("1"), Coin::minor("2")); + assert_eq!(Coin::major("1") + Coin::major("1"), Coin::major("2")); + assert_eq!(Coin::minor("1") + Coin::major("1"), Coin::minor("1000001")); + assert_eq!(Coin::major("1") + Coin::minor("1"), Coin::major("1.000001")); + } + + #[test] + fn test_sub() { + assert_eq!(Coin::minor("1") - Coin::minor("1"), Coin::minor("0")); + assert_eq!(Coin::major("1") - Coin::major("1"), Coin::major("0")); + assert_eq!(Coin::minor("1") - Coin::major("1"), Coin::minor("-999999")); + assert_eq!(Coin::major("1") - Coin::minor("1"), Coin::major("0.999999")); +} +}