From 2dfbd2f71449b4b62bb5142d14de7d4bfc9855f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 10:57:49 +0100 Subject: [PATCH 01/15] Removed rustfmt.toml file and adjusted wallet formatting (#1293) --- nym-wallet/src-tauri/rustfmt.toml | 13 - nym-wallet/src-tauri/src/build.rs | 2 +- nym-wallet/src-tauri/src/coin.rs | 680 ++-- nym-wallet/src-tauri/src/config/mod.rs | 797 +++-- nym-wallet/src-tauri/src/error.rs | 214 +- nym-wallet/src-tauri/src/main.rs | 242 +- nym-wallet/src-tauri/src/menu.rs | 36 +- nym-wallet/src-tauri/src/network.rs | 64 +- nym-wallet/src-tauri/src/network_config.rs | 116 +- .../src/operations/mixnet/account.rs | 932 +++--- .../src-tauri/src/operations/mixnet/admin.rs | 60 +- .../src-tauri/src/operations/mixnet/bond.rs | 100 +- .../src/operations/mixnet/delegate.rs | 92 +- .../src-tauri/src/operations/mixnet/epoch.rs | 28 +- .../src-tauri/src/operations/mixnet/send.rs | 78 +- .../src/operations/simulate/admin.rs | 28 +- .../src/operations/simulate/cosmos.rs | 36 +- .../src/operations/simulate/mixnet.rs | 226 +- .../src-tauri/src/operations/simulate/mod.rs | 66 +- .../src/operations/simulate/vesting.rs | 192 +- .../src/operations/validator_api/status.rs | 74 +- .../src-tauri/src/operations/vesting/bond.rs | 92 +- .../src/operations/vesting/delegate.rs | 78 +- .../src-tauri/src/operations/vesting/mod.rs | 94 +- .../src/operations/vesting/queries.rs | 216 +- nym-wallet/src-tauri/src/state.rs | 706 +++-- nym-wallet/src-tauri/src/utils.rs | 128 +- .../src/wallet_storage/account_data.rs | 485 +-- .../src/wallet_storage/encryption.rs | 316 +- .../src-tauri/src/wallet_storage/mod.rs | 2777 +++++++++-------- .../src-tauri/src/wallet_storage/password.rs | 84 +- 31 files changed, 4506 insertions(+), 4546 deletions(-) delete mode 100644 nym-wallet/src-tauri/rustfmt.toml diff --git a/nym-wallet/src-tauri/rustfmt.toml b/nym-wallet/src-tauri/rustfmt.toml deleted file mode 100644 index 45642c1904..0000000000 --- a/nym-wallet/src-tauri/rustfmt.toml +++ /dev/null @@ -1,13 +0,0 @@ -max_width = 100 -hard_tabs = false -tab_spaces = 2 -newline_style = "Auto" -use_small_heuristics = "Default" -reorder_imports = true -reorder_modules = true -remove_nested_parens = true -edition = "2018" -merge_derives = true -use_try_shorthand = false -use_field_init_shorthand = false -force_explicit_abi = true diff --git a/nym-wallet/src-tauri/src/build.rs b/nym-wallet/src-tauri/src/build.rs index 795b9b7c83..d860e1e6a7 100644 --- a/nym-wallet/src-tauri/src/build.rs +++ b/nym-wallet/src-tauri/src/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index 3e2078c069..83ed92c807 100644 --- a/nym-wallet/src-tauri/src/coin.rs +++ b/nym-wallet/src-tauri/src/coin.rs @@ -17,403 +17,403 @@ use validator_client::nymd::Denom as CosmosDenom; #[cfg_attr(test, ts(export, export, export_to = "../src/types/rust/denom.ts"))] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum Denom { - Major, - Minor, + Major, + Minor, } const MINOR_IN_MAJOR: f64 = 1_000_000.; impl FromStr for Denom { - type Err = BackendError; + type Err = BackendError; - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - for network in Network::iter() { - let denom = network.denom(); - if s == denom.as_ref().to_lowercase() || s == "minor" { - return Ok(Denom::Minor); - } else if s == denom.as_ref()[1..].to_lowercase() || s == "major" { - return Ok(Denom::Major); - } + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + for network in Network::iter() { + let denom = network.denom(); + if s == denom.as_ref().to_lowercase() || s == "minor" { + return Ok(Denom::Minor); + } else if s == denom.as_ref()[1..].to_lowercase() || s == "major" { + return Ok(Denom::Major); + } + } + Err(BackendError::InvalidDenom(s)) } - Err(BackendError::InvalidDenom(s)) - } } impl TryFrom for Denom { - type Error = BackendError; + type Error = BackendError; - fn try_from(value: CosmosDenom) -> Result { - Denom::from_str(&value.to_string()) - } + fn try_from(value: CosmosDenom) -> Result { + Denom::from_str(&value.to_string()) + } } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/coin.ts"))] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct Coin { - amount: String, - denom: Denom, + amount: String, + denom: Denom, } // Allows adding minor and major denominations, output will have the LHS denom. impl Add for Coin { - type Output = Self; + type Output = Self; - fn add(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, + fn add(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, + } } - } } // Allows adding minor and major denominations, output will have the LHS denom. impl Sub for Coin { - type Output = Self; + 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, + 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 { - #[allow(unused)] - pub fn major(amount: T) -> Coin { - Coin { - amount: amount.to_string(), - denom: Denom::Major, - } - } - - #[allow(unused)] - 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() / MINOR_IN_MAJOR).to_string(), - denom: Denom::Major, - }, - } - } - - pub fn to_minor(&self) -> Coin { - match self.denom { - Denom::Minor => self.clone(), - Denom::Major => Coin { - amount: (self.amount.parse::().unwrap() * MINOR_IN_MAJOR).to_string(), - denom: Denom::Minor, - }, - } - } - - pub fn amount(&self) -> String { - self.amount.clone() - } - - #[allow(unused)] - pub fn denom(&self) -> Denom { - self.denom.clone() - } - - // Helper function that returns the local denom in terms of the specified network denom. - fn denom_as_string(&self, network_denom: &CosmosDenom) -> Result { - // Currently there is the widespread assumption that network denomination is always in - // `Denom::Minor`, and starts with 'u'. - let network_denom = network_denom.to_string(); - if !network_denom.starts_with('u') { - return Err(BackendError::InvalidNetworkDenom(network_denom)); + #[allow(unused)] + pub fn major(amount: T) -> Coin { + Coin { + amount: amount.to_string(), + denom: Denom::Major, + } } - Ok(match &self.denom { - Denom::Minor => network_denom, - Denom::Major => network_denom[1..].to_string(), - }) - } - - pub fn into_cosmos_coin(self, network_denom: &CosmosDenom) -> Result { - match Decimal::from_str(&self.amount) { - Ok(amount) => Ok(CosmosCoin { - amount, - denom: CosmosDenom::from_str(&self.denom_as_string(network_denom)?)?, - }), - Err(e) => Err(e.into()), + #[allow(unused)] + pub fn minor(amount: T) -> Coin { + Coin { + amount: amount.to_string(), + denom: Denom::Minor, + } } - } - pub fn into_cosmwasm_coin( - self, - network_denom: &CosmosDenom, - ) -> Result { - Ok(CosmWasmCoin { - denom: self.denom_as_string(network_denom)?, - amount: Uint128::try_from(self.amount.as_str())?, - }) - } + 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() / MINOR_IN_MAJOR).to_string(), + denom: Denom::Major, + }, + } + } + + pub fn to_minor(&self) -> Coin { + match self.denom { + Denom::Minor => self.clone(), + Denom::Major => Coin { + amount: (self.amount.parse::().unwrap() * MINOR_IN_MAJOR).to_string(), + denom: Denom::Minor, + }, + } + } + + pub fn amount(&self) -> String { + self.amount.clone() + } + + #[allow(unused)] + pub fn denom(&self) -> Denom { + self.denom.clone() + } + + // Helper function that returns the local denom in terms of the specified network denom. + fn denom_as_string(&self, network_denom: &CosmosDenom) -> Result { + // Currently there is the widespread assumption that network denomination is always in + // `Denom::Minor`, and starts with 'u'. + let network_denom = network_denom.to_string(); + if !network_denom.starts_with('u') { + return Err(BackendError::InvalidNetworkDenom(network_denom)); + } + + Ok(match &self.denom { + Denom::Minor => network_denom, + Denom::Major => network_denom[1..].to_string(), + }) + } + + pub fn into_cosmos_coin(self, network_denom: &CosmosDenom) -> Result { + match Decimal::from_str(&self.amount) { + Ok(amount) => Ok(CosmosCoin { + amount, + denom: CosmosDenom::from_str(&self.denom_as_string(network_denom)?)?, + }), + Err(e) => Err(e.into()), + } + } + + pub fn into_cosmwasm_coin( + self, + network_denom: &CosmosDenom, + ) -> Result { + Ok(CosmWasmCoin { + denom: self.denom_as_string(network_denom)?, + amount: Uint128::try_from(self.amount.as_str())?, + }) + } } impl From for Coin { - fn from(c: CosmosCoin) -> Coin { - Coin { - amount: c.amount.to_string(), - denom: Denom::from_str(c.denom.as_ref()).unwrap(), + fn from(c: CosmosCoin) -> Coin { + Coin { + amount: c.amount.to_string(), + denom: Denom::from_str(c.denom.as_ref()).unwrap(), + } } - } } impl From for Coin { - fn from(c: CosmWasmCoin) -> Coin { - Coin { - amount: c.amount.to_string(), - denom: Denom::from_str(&c.denom).unwrap(), + fn from(c: CosmWasmCoin) -> Coin { + Coin { + amount: c.amount.to_string(), + denom: Denom::from_str(&c.denom).unwrap(), + } } - } } #[cfg(test)] mod test { - use super::*; - use crate::error::BackendError; - use cosmwasm_std::Coin as CosmWasmCoin; - use serde_json::json; - use std::convert::TryFrom; - use std::str::FromStr; + use super::*; + use crate::error::BackendError; + use cosmwasm_std::Coin as CosmWasmCoin; + use serde_json::json; + use std::convert::TryFrom; + use std::str::FromStr; - #[test] - fn json_to_coin() { - let minor = json!({ - "amount": "1", - "denom": "Minor" - }); + #[test] + fn json_to_coin() { + let minor = json!({ + "amount": "1", + "denom": "Minor" + }); - let major = json!({ - "amount": "1", - "denom": "Major" - }); + let major = json!({ + "amount": "1", + "denom": "Major" + }); - let test_minor_coin = Coin::minor("1"); - let test_major_coin = Coin::major("1"); + 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(); + 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_from_str() { - assert_eq!(Denom::from_str("unym").unwrap(), Denom::Minor); - assert_eq!(Denom::from_str("nym").unwrap(), Denom::Major); - assert_eq!(Denom::from_str("minor").unwrap(), Denom::Minor); - assert_eq!(Denom::from_str("major").unwrap(), Denom::Major); - - assert!(matches!( - Denom::from_str("foo").unwrap_err(), - BackendError::InvalidDenom { .. }, - )); - } - - #[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")); - } - - #[test] - fn network_denom_is_assumed_to_be_in_minor_denom() { - let network_denom = CosmosDenom::from_str("nym").unwrap(); - assert!(matches!( - Coin::minor("42") - .denom_as_string(&network_denom) - .unwrap_err(), - BackendError::InvalidNetworkDenom { .. } - )); - } - - #[test] - fn local_denom_to_interpreted_using_network_denom() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); - assert_eq!( - Coin::minor("42").denom_as_string(&network_denom).unwrap(), - "unym", - ); - assert_eq!( - Coin::major("42").denom_as_string(&network_denom).unwrap(), - "nym", - ); - } - - #[test] - fn coin_to_coin_minor() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); - let coin = Coin::minor("42"); - - let cosmoswasm_coin = coin.clone().into_cosmwasm_coin(&network_denom).unwrap(); - assert_eq!(cosmoswasm_coin, CosmWasmCoin::new(42, "unym"),); - - let cosmos_coin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - denom: CosmosDenom::from_str("unym").unwrap(), - amount: Decimal::from_str("42").unwrap(), - }, - ); - } - - #[test] - fn coin_to_coin_major() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); - let coin = Coin::major("52"); - - let cosmoswasm_coin = coin.clone().into_cosmwasm_coin(&network_denom).unwrap(); - assert_eq!(cosmoswasm_coin, CosmWasmCoin::new(52, "nym"),); - - let cosmos_coin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - denom: CosmosDenom::from_str("nym").unwrap(), - amount: Decimal::from_str("52").unwrap(), - }, - ); - } - - 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() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); - for amount in amounts() { - let coin = Coin::minor(amount); - let cosmoswasm_coin: CosmWasmCoin = coin.into_cosmwasm_coin(&network_denom).unwrap(); - assert_eq!( - cosmoswasm_coin, - CosmWasmCoin::new(amount.parse::().unwrap(), "unym") - ); - assert_eq!( - Coin::try_from(cosmoswasm_coin).unwrap(), - Coin::minor(amount) - ); - - let coin = Coin::major(amount); - let cosmoswasm_coin: CosmWasmCoin = coin.into_cosmwasm_coin(&network_denom).unwrap(); - assert_eq!( - cosmoswasm_coin, - CosmWasmCoin::new(amount.parse::().unwrap(), "nym") - ); - assert_eq!( - Coin::try_from(cosmoswasm_coin).unwrap(), - Coin::major(amount) - ); + assert_eq!(minor_coin, test_minor_coin); + assert_eq!(major_coin, test_major_coin); } - } - #[test] - fn coin_to_cosmos() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); - for amount in amounts() { - let coin = Coin::minor(amount); - let cosmos_coin: CosmosCoin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - amount: Decimal::from_str(amount).unwrap(), - denom: CosmosDenom::from_str("unym").unwrap() - } - ); - assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::minor(amount)); + #[test] + fn denom_from_str() { + assert_eq!(Denom::from_str("unym").unwrap(), Denom::Minor); + assert_eq!(Denom::from_str("nym").unwrap(), Denom::Major); + assert_eq!(Denom::from_str("minor").unwrap(), Denom::Minor); + assert_eq!(Denom::from_str("major").unwrap(), Denom::Major); - let coin = Coin::major(amount); - let cosmos_coin: CosmosCoin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - amount: Decimal::from_str(amount).unwrap(), - denom: CosmosDenom::from_str("nym").unwrap() - } - ); - assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::major(amount)); + assert!(matches!( + Denom::from_str("foo").unwrap_err(), + BackendError::InvalidDenom { .. }, + )); } - } - #[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 denom_conversions() { + let minor = Coin::minor("1"); + let major = minor.to_major(); - #[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")); - } + assert_eq!(major, Coin::major("0.000001")); + + let minor = major.to_minor(); + assert_eq!(minor, Coin::minor("1")); + } + + #[test] + fn network_denom_is_assumed_to_be_in_minor_denom() { + let network_denom = CosmosDenom::from_str("nym").unwrap(); + assert!(matches!( + Coin::minor("42") + .denom_as_string(&network_denom) + .unwrap_err(), + BackendError::InvalidNetworkDenom { .. } + )); + } + + #[test] + fn local_denom_to_interpreted_using_network_denom() { + let network_denom = CosmosDenom::from_str("unym").unwrap(); + assert_eq!( + Coin::minor("42").denom_as_string(&network_denom).unwrap(), + "unym", + ); + assert_eq!( + Coin::major("42").denom_as_string(&network_denom).unwrap(), + "nym", + ); + } + + #[test] + fn coin_to_coin_minor() { + let network_denom = CosmosDenom::from_str("unym").unwrap(); + let coin = Coin::minor("42"); + + let cosmoswasm_coin = coin.clone().into_cosmwasm_coin(&network_denom).unwrap(); + assert_eq!(cosmoswasm_coin, CosmWasmCoin::new(42, "unym"),); + + let cosmos_coin = coin.into_cosmos_coin(&network_denom).unwrap(); + assert_eq!( + cosmos_coin, + CosmosCoin { + denom: CosmosDenom::from_str("unym").unwrap(), + amount: Decimal::from_str("42").unwrap(), + }, + ); + } + + #[test] + fn coin_to_coin_major() { + let network_denom = CosmosDenom::from_str("unym").unwrap(); + let coin = Coin::major("52"); + + let cosmoswasm_coin = coin.clone().into_cosmwasm_coin(&network_denom).unwrap(); + assert_eq!(cosmoswasm_coin, CosmWasmCoin::new(52, "nym"),); + + let cosmos_coin = coin.into_cosmos_coin(&network_denom).unwrap(); + assert_eq!( + cosmos_coin, + CosmosCoin { + denom: CosmosDenom::from_str("nym").unwrap(), + amount: Decimal::from_str("52").unwrap(), + }, + ); + } + + 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() { + let network_denom = CosmosDenom::from_str("unym").unwrap(); + for amount in amounts() { + let coin = Coin::minor(amount); + let cosmoswasm_coin: CosmWasmCoin = coin.into_cosmwasm_coin(&network_denom).unwrap(); + assert_eq!( + cosmoswasm_coin, + CosmWasmCoin::new(amount.parse::().unwrap(), "unym") + ); + assert_eq!( + Coin::try_from(cosmoswasm_coin).unwrap(), + Coin::minor(amount) + ); + + let coin = Coin::major(amount); + let cosmoswasm_coin: CosmWasmCoin = coin.into_cosmwasm_coin(&network_denom).unwrap(); + assert_eq!( + cosmoswasm_coin, + CosmWasmCoin::new(amount.parse::().unwrap(), "nym") + ); + assert_eq!( + Coin::try_from(cosmoswasm_coin).unwrap(), + Coin::major(amount) + ); + } + } + + #[test] + fn coin_to_cosmos() { + let network_denom = CosmosDenom::from_str("unym").unwrap(); + for amount in amounts() { + let coin = Coin::minor(amount); + let cosmos_coin: CosmosCoin = coin.into_cosmos_coin(&network_denom).unwrap(); + assert_eq!( + cosmos_coin, + CosmosCoin { + amount: Decimal::from_str(amount).unwrap(), + denom: CosmosDenom::from_str("unym").unwrap() + } + ); + assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::minor(amount)); + + let coin = Coin::major(amount); + let cosmos_coin: CosmosCoin = coin.into_cosmos_coin(&network_denom).unwrap(); + assert_eq!( + cosmos_coin, + CosmosCoin { + amount: Decimal::from_str(amount).unwrap(), + denom: CosmosDenom::from_str("nym").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")); + } } diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index f4dc16a9c5..cf8cde54fe 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -18,7 +18,7 @@ use url::Url; use validator_client::nymd::AccountId as CosmosAccountId; pub const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str = - "https://nymtech.net/.wellknown/wallet/validators.json"; + "https://nymtech.net/.wellknown/wallet/validators.json"; const CURRENT_GLOBAL_CONFIG_VERSION: u32 = 1; const CURRENT_NETWORK_CONFIG_VERSION: u32 = 1; @@ -26,448 +26,445 @@ pub(crate) const CUSTOM_SIMULATED_GAS_MULTIPLIER: f32 = 1.4; #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Config { - // Base configuration is not part of the configuration file as it's not intended to be changed. - base: Base, + // Base configuration is not part of the configuration file as it's not intended to be changed. + base: Base, - // Global configuration file - global: Option, + // Global configuration file + global: Option, - // One configuration file per network - networks: HashMap, + // One configuration file per network + networks: HashMap, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] struct Base { - /// Information on all the networks that the wallet connects to. - networks: SupportedNetworks, + /// Information on all the networks that the wallet connects to. + networks: SupportedNetworks, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct GlobalConfig { - version: Option, - // TODO: there are no global settings (yet) + version: Option, + // TODO: there are no global settings (yet) } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct NetworkConfig { - version: Option, + version: Option, - // User selected urls - selected_nymd_url: Option, - selected_api_url: Option, + // User selected urls + selected_nymd_url: Option, + selected_api_url: Option, - // Additional user provided validators. - // It is an option for the purpuse of file serialization. - validator_urls: Option>, + // Additional user provided validators. + // It is an option for the purpuse of file serialization. + validator_urls: Option>, } impl Default for Base { - fn default() -> Self { - let networks = WalletNetwork::iter().map(Into::into).collect(); - Base { - networks: SupportedNetworks::new(networks), + fn default() -> Self { + let networks = WalletNetwork::iter().map(Into::into).collect(); + Base { + networks: SupportedNetworks::new(networks), + } } - } } impl Default for GlobalConfig { - fn default() -> Self { - Self { - version: Some(CURRENT_GLOBAL_CONFIG_VERSION), + fn default() -> Self { + Self { + version: Some(CURRENT_GLOBAL_CONFIG_VERSION), + } } - } } impl Default for NetworkConfig { - fn default() -> Self { - Self { - version: Some(CURRENT_NETWORK_CONFIG_VERSION), - selected_nymd_url: None, - selected_api_url: None, - validator_urls: None, + fn default() -> Self { + Self { + version: Some(CURRENT_NETWORK_CONFIG_VERSION), + selected_nymd_url: None, + selected_api_url: None, + validator_urls: None, + } } - } } impl NetworkConfig { - fn validators(&self) -> impl Iterator { - self.validator_urls.iter().flat_map(|v| v.iter()) - } + fn validators(&self) -> impl Iterator { + self.validator_urls.iter().flat_map(|v| v.iter()) + } } impl Config { - fn root_directory() -> PathBuf { - tauri::api::path::config_dir().expect("Failed to get config directory") - } - - fn config_directory() -> PathBuf { - Self::root_directory().join(CONFIG_DIR_NAME) - } - - fn config_file_path(network: Option) -> PathBuf { - if let Some(network) = network { - let network_filename = format!("{}.toml", network.as_key()); - Self::config_directory().join(network_filename) - } else { - Self::config_directory().join(CONFIG_FILENAME) - } - } - - pub fn save_to_files(&self) -> io::Result<()> { - log::trace!("Config::save_to_file"); - - // Make sure the whole directory structure actually exists - fs::create_dir_all(Self::config_directory())?; - - // Global config - if let Some(global) = &self.global { - let location = Self::config_file_path(None); - - match toml::to_string_pretty(&global) - .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) - .map(|toml| fs::write(location.clone(), toml)) - { - Ok(_) => log::debug!("Writing to: {:#?}", location), - Err(err) => log::warn!("Failed to write to {:#?}: {err}", location), - } + fn root_directory() -> PathBuf { + tauri::api::path::config_dir().expect("Failed to get config directory") } - // One file per network - for (network, config) in &self.networks { - let network = match Network::from_str(network).map(Into::into) { - Ok(network) => network, - Err(err) => { - log::warn!("Unexpected name for network configuration, not saving: {err}"); - break; + fn config_directory() -> PathBuf { + Self::root_directory().join(CONFIG_DIR_NAME) + } + + fn config_file_path(network: Option) -> PathBuf { + if let Some(network) = network { + let network_filename = format!("{}.toml", network.as_key()); + Self::config_directory().join(network_filename) + } else { + Self::config_directory().join(CONFIG_FILENAME) } - }; - - let location = Self::config_file_path(Some(network)); - match toml::to_string_pretty(config) - .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) - .map(|toml| fs::write(location.clone(), toml)) - { - Ok(_) => log::debug!("Writing to: {:#?}", location), - Err(err) => log::warn!("Failed to write to {:#?}: {err}", location), - } } - Ok(()) - } - pub fn load_from_files() -> Self { - // Global - let global = { - let file = Self::config_file_path(None); - match load_from_file::(file.clone()) { - Ok(global) => { - log::debug!("Loaded from file {:#?}", file); - Some(global) + pub fn save_to_files(&self) -> io::Result<()> { + log::trace!("Config::save_to_file"); + + // Make sure the whole directory structure actually exists + fs::create_dir_all(Self::config_directory())?; + + // Global config + if let Some(global) = &self.global { + let location = Self::config_file_path(None); + + match toml::to_string_pretty(&global) + .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) + .map(|toml| fs::write(location.clone(), toml)) + { + Ok(_) => log::debug!("Writing to: {:#?}", location), + Err(err) => log::warn!("Failed to write to {:#?}: {err}", location), + } } - Err(err) => { - log::trace!("Not loading {:#?}: {}", file, err); - None + + // One file per network + for (network, config) in &self.networks { + let network = match Network::from_str(network).map(Into::into) { + Ok(network) => network, + Err(err) => { + log::warn!("Unexpected name for network configuration, not saving: {err}"); + break; + } + }; + + let location = Self::config_file_path(Some(network)); + match toml::to_string_pretty(config) + .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) + .map(|toml| fs::write(location.clone(), toml)) + { + Ok(_) => log::debug!("Writing to: {:#?}", location), + Err(err) => log::warn!("Failed to write to {:#?}: {err}", location), + } } - } - }; + Ok(()) + } - // One file per network - let mut networks = HashMap::new(); - for network in WalletNetwork::iter() { - let file = Self::config_file_path(Some(network)); - match load_from_file::(file.clone()) { - Ok(config) => { - log::trace!("Loaded from file {:#?}", file); - networks.insert(network.as_key(), config); + pub fn load_from_files() -> Self { + // Global + let global = { + let file = Self::config_file_path(None); + match load_from_file::(file.clone()) { + Ok(global) => { + log::debug!("Loaded from file {:#?}", file); + Some(global) + } + Err(err) => { + log::trace!("Not loading {:#?}: {}", file, err); + None + } + } + }; + + // One file per network + let mut networks = HashMap::new(); + for network in WalletNetwork::iter() { + let file = Self::config_file_path(Some(network)); + match load_from_file::(file.clone()) { + Ok(config) => { + log::trace!("Loaded from file {:#?}", file); + networks.insert(network.as_key(), config); + } + Err(err) => log::trace!("Not loading {:#?}: {}", file, err), + }; + } + + Self { + base: Base::default(), + global, + networks, } - Err(err) => log::trace!("Not loading {:#?}: {}", file, err), - }; } - Self { - base: Base::default(), - global, - networks, + pub fn get_base_validators( + &self, + network: WalletNetwork, + ) -> impl Iterator + '_ { + self.base.networks.validators(network.into()).map(|v| { + v.clone() + .try_into() + .expect("The hardcoded validators are assumed to be valid urls") + }) } - } - pub fn get_base_validators( - &self, - network: WalletNetwork, - ) -> impl Iterator + '_ { - self.base.networks.validators(network.into()).map(|v| { - v.clone() - .try_into() - .expect("The hardcoded validators are assumed to be valid urls") - }) - } - - pub fn get_configured_validators( - &self, - network: WalletNetwork, - ) -> impl Iterator + '_ { - self - .networks - .get(&network.as_key()) - .into_iter() - .flat_map(|c| c.validators().cloned()) - } - - pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option { - self - .base - .networks - .mixnet_contract_address(network.into()) - .expect("No mixnet contract address found in config") - .parse() - .ok() - } - - pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> Option { - self - .base - .networks - .vesting_contract_address(network.into()) - .expect("No vesting contract address found in config") - .parse() - .ok() - } - - pub fn get_bandwidth_claim_contract_address( - &self, - network: WalletNetwork, - ) -> Option { - self - .base - .networks - .bandwidth_claim_contract_address(network.into()) - .expect("No bandwidth claim contract address found in config") - .parse() - .ok() - } - - pub fn select_validator_nymd_url(&mut self, nymd_url: Url, network: WalletNetwork) { - if let Some(net) = self.networks.get_mut(&network.as_key()) { - net.selected_nymd_url = Some(nymd_url); - } else { - self.networks.insert( - network.as_key(), - NetworkConfig { - selected_nymd_url: Some(nymd_url), - ..NetworkConfig::default() - }, - ); + pub fn get_configured_validators( + &self, + network: WalletNetwork, + ) -> impl Iterator + '_ { + self.networks + .get(&network.as_key()) + .into_iter() + .flat_map(|c| c.validators().cloned()) } - } - pub fn select_validator_api_url(&mut self, api_url: Url, network: WalletNetwork) { - if let Some(net) = self.networks.get_mut(&network.as_key()) { - net.selected_api_url = Some(api_url); - } else { - self.networks.insert( - network.as_key(), - NetworkConfig { - selected_nymd_url: Some(api_url), - ..NetworkConfig::default() - }, - ); + pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option { + self.base + .networks + .mixnet_contract_address(network.into()) + .expect("No mixnet contract address found in config") + .parse() + .ok() } - } - pub fn get_selected_validator_nymd_url(&self, network: WalletNetwork) -> Option { - self - .networks - .get(&network.as_key()) - .and_then(|config| config.selected_nymd_url.clone()) - } - - pub fn get_selected_validator_api_url(&self, network: &WalletNetwork) -> Option { - self - .networks - .get(&network.as_key()) - .and_then(|config| config.selected_api_url.clone()) - } - - pub fn add_validator_url(&mut self, url: ValidatorConfigEntry, network: WalletNetwork) { - if let Some(network_config) = self.networks.get_mut(&network.as_key()) { - if let Some(ref mut urls) = network_config.validator_urls { - urls.push(url); - } else { - network_config.validator_urls = Some(vec![url]); - } - } else { - self.networks.insert( - network.as_key(), - NetworkConfig { - validator_urls: Some(vec![url]), - ..NetworkConfig::default() - }, - ); + pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> Option { + self.base + .networks + .vesting_contract_address(network.into()) + .expect("No vesting contract address found in config") + .parse() + .ok() } - } - pub fn remove_validator_url(&mut self, url: ValidatorConfigEntry, network: WalletNetwork) { - if let Some(network_config) = self.networks.get_mut(&network.as_key()) { - if let Some(ref mut urls) = network_config.validator_urls { - // Removes duplicates too if there are any - urls.retain(|existing_url| existing_url != &url); - } + pub fn get_bandwidth_claim_contract_address( + &self, + network: WalletNetwork, + ) -> Option { + self.base + .networks + .bandwidth_claim_contract_address(network.into()) + .expect("No bandwidth claim contract address found in config") + .parse() + .ok() + } + + pub fn select_validator_nymd_url(&mut self, nymd_url: Url, network: WalletNetwork) { + if let Some(net) = self.networks.get_mut(&network.as_key()) { + net.selected_nymd_url = Some(nymd_url); + } else { + self.networks.insert( + network.as_key(), + NetworkConfig { + selected_nymd_url: Some(nymd_url), + ..NetworkConfig::default() + }, + ); + } + } + + pub fn select_validator_api_url(&mut self, api_url: Url, network: WalletNetwork) { + if let Some(net) = self.networks.get_mut(&network.as_key()) { + net.selected_api_url = Some(api_url); + } else { + self.networks.insert( + network.as_key(), + NetworkConfig { + selected_nymd_url: Some(api_url), + ..NetworkConfig::default() + }, + ); + } + } + + pub fn get_selected_validator_nymd_url(&self, network: WalletNetwork) -> Option { + self.networks + .get(&network.as_key()) + .and_then(|config| config.selected_nymd_url.clone()) + } + + pub fn get_selected_validator_api_url(&self, network: &WalletNetwork) -> Option { + self.networks + .get(&network.as_key()) + .and_then(|config| config.selected_api_url.clone()) + } + + pub fn add_validator_url(&mut self, url: ValidatorConfigEntry, network: WalletNetwork) { + if let Some(network_config) = self.networks.get_mut(&network.as_key()) { + if let Some(ref mut urls) = network_config.validator_urls { + urls.push(url); + } else { + network_config.validator_urls = Some(vec![url]); + } + } else { + self.networks.insert( + network.as_key(), + NetworkConfig { + validator_urls: Some(vec![url]), + ..NetworkConfig::default() + }, + ); + } + } + + pub fn remove_validator_url(&mut self, url: ValidatorConfigEntry, network: WalletNetwork) { + if let Some(network_config) = self.networks.get_mut(&network.as_key()) { + if let Some(ref mut urls) = network_config.validator_urls { + // Removes duplicates too if there are any + urls.retain(|existing_url| existing_url != &url); + } + } } - } } fn load_from_file(file: PathBuf) -> Result where - T: DeserializeOwned, + T: DeserializeOwned, { - fs::read_to_string(file).and_then(|contents| { - toml::from_str::(&contents) - .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) - }) + fs::read_to_string(file).and_then(|contents| { + toml::from_str::(&contents) + .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) + }) } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct ValidatorConfigEntry { - pub nymd_url: Url, - pub nymd_name: Option, - pub api_url: Option, + pub nymd_url: Url, + pub nymd_name: Option, + pub api_url: Option, } impl TryFrom for ValidatorConfigEntry { - type Error = BackendError; + type Error = BackendError; - fn try_from(validator: ValidatorDetails) -> Result { - Ok(ValidatorConfigEntry { - nymd_url: validator.nymd_url.parse()?, - nymd_name: None, - api_url: match &validator.api_url { - Some(url) => Some(url.parse()?), - None => None, - }, - }) - } + fn try_from(validator: ValidatorDetails) -> Result { + Ok(ValidatorConfigEntry { + nymd_url: validator.nymd_url.parse()?, + nymd_name: None, + api_url: match &validator.api_url { + Some(url) => Some(url.parse()?), + None => None, + }, + }) + } } impl TryFrom for ValidatorConfigEntry { - type Error = BackendError; + type Error = BackendError; - fn try_from(validator: network_config::Validator) -> Result { - Ok(ValidatorConfigEntry { - nymd_url: validator.nymd_url.parse()?, - nymd_name: validator.nymd_name, - api_url: match &validator.api_url { - Some(url) => Some(url.parse()?), - None => None, - }, - }) - } + fn try_from(validator: network_config::Validator) -> Result { + Ok(ValidatorConfigEntry { + nymd_url: validator.nymd_url.parse()?, + nymd_name: validator.nymd_name, + api_url: match &validator.api_url { + Some(url) => Some(url.parse()?), + None => None, + }, + }) + } } impl fmt::Display for ValidatorConfigEntry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s1 = format!("nymd_url: {}", self.nymd_url); - let name = self.nymd_name.as_ref().map(|name| format!(" ({})", name)); - let s2 = self - .api_url - .as_ref() - .map(|url| format!(", api_url: {}", url)); - write!( - f, - " {}{}{},", - s1, - name.unwrap_or_default(), - s2.unwrap_or_default() - ) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s1 = format!("nymd_url: {}", self.nymd_url); + let name = self.nymd_name.as_ref().map(|name| format!(" ({})", name)); + let s2 = self + .api_url + .as_ref() + .map(|url| format!(", api_url: {}", url)); + write!( + f, + " {}{}{},", + s1, + name.unwrap_or_default(), + s2.unwrap_or_default() + ) + } } #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct OptionalValidators { - // User supplied additional validator urls in addition to the hardcoded ones. - // These are separate fields, rather than a map, to force the serialization order. - mainnet: Option>, - sandbox: Option>, - qa: Option>, + // User supplied additional validator urls in addition to the hardcoded ones. + // These are separate fields, rather than a map, to force the serialization order. + mainnet: Option>, + sandbox: Option>, + qa: Option>, } impl OptionalValidators { - pub fn validators(&self, network: WalletNetwork) -> impl Iterator { - match network { - WalletNetwork::MAINNET => self.mainnet.as_ref(), - WalletNetwork::SANDBOX => self.sandbox.as_ref(), - WalletNetwork::QA => self.qa.as_ref(), + pub fn validators( + &self, + network: WalletNetwork, + ) -> impl Iterator { + match network { + WalletNetwork::MAINNET => self.mainnet.as_ref(), + WalletNetwork::SANDBOX => self.sandbox.as_ref(), + WalletNetwork::QA => self.qa.as_ref(), + } + .into_iter() + .flatten() } - .into_iter() - .flatten() - } } impl fmt::Display for OptionalValidators { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s1 = self - .mainnet - .as_ref() - .map(|validators| format!("mainnet: [\n{}\n]", validators.iter().format("\n"))) - .unwrap_or_default(); - let s2 = self - .sandbox - .as_ref() - .map(|validators| format!(",\nsandbox: [\n{}\n]", validators.iter().format("\n"))) - .unwrap_or_default(); - let s3 = self - .qa - .as_ref() - .map(|validators| format!(",\nqa: [\n{}\n]", validators.iter().format("\n"))) - .unwrap_or_default(); - write!(f, "{}{}{}", s1, s2, s3) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s1 = self + .mainnet + .as_ref() + .map(|validators| format!("mainnet: [\n{}\n]", validators.iter().format("\n"))) + .unwrap_or_default(); + let s2 = self + .sandbox + .as_ref() + .map(|validators| format!(",\nsandbox: [\n{}\n]", validators.iter().format("\n"))) + .unwrap_or_default(); + let s3 = self + .qa + .as_ref() + .map(|validators| format!(",\nqa: [\n{}\n]", validators.iter().format("\n"))) + .unwrap_or_default(); + write!(f, "{}{}{}", s1, s2, s3) + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - fn test_config() -> Config { - let netconfig = NetworkConfig { - selected_nymd_url: None, - selected_api_url: Some("https://my_api_url.com".parse().unwrap()), + fn test_config() -> Config { + let netconfig = NetworkConfig { + selected_nymd_url: None, + selected_api_url: Some("https://my_api_url.com".parse().unwrap()), - validator_urls: Some(vec![ - ValidatorConfigEntry { - nymd_url: "https://foo".parse().unwrap(), - nymd_name: Some("FooName".to_string()), - api_url: None, - }, - ValidatorConfigEntry { - nymd_url: "https://bar".parse().unwrap(), - nymd_name: None, - api_url: Some("https://bar/api".parse().unwrap()), - }, - ValidatorConfigEntry { - nymd_url: "https://baz".parse().unwrap(), - nymd_name: None, - api_url: Some("https://baz/api".parse().unwrap()), - }, - ]), - ..NetworkConfig::default() - }; + validator_urls: Some(vec![ + ValidatorConfigEntry { + nymd_url: "https://foo".parse().unwrap(), + nymd_name: Some("FooName".to_string()), + api_url: None, + }, + ValidatorConfigEntry { + nymd_url: "https://bar".parse().unwrap(), + nymd_name: None, + api_url: Some("https://bar/api".parse().unwrap()), + }, + ValidatorConfigEntry { + nymd_url: "https://baz".parse().unwrap(), + nymd_name: None, + api_url: Some("https://baz/api".parse().unwrap()), + }, + ]), + ..NetworkConfig::default() + }; - Config { - base: Base::default(), - global: Some(GlobalConfig::default()), - networks: [(WalletNetwork::MAINNET.as_key(), netconfig)] - .into_iter() - .collect(), + Config { + base: Base::default(), + global: Some(GlobalConfig::default()), + networks: [(WalletNetwork::MAINNET.as_key(), netconfig)] + .into_iter() + .collect(), + } } - } - #[test] - fn serialize_to_toml() { - let config = test_config(); - let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()]; - assert_eq!( - toml::to_string_pretty(netconfig).unwrap(), - r#"version = 1 + #[test] + fn serialize_to_toml() { + let config = test_config(); + let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()]; + assert_eq!( + toml::to_string_pretty(netconfig).unwrap(), + r#"version = 1 selected_api_url = 'https://my_api_url.com/' [[validator_urls]] @@ -482,17 +479,17 @@ api_url = 'https://bar/api' nymd_url = 'https://baz/' api_url = 'https://baz/api' "# - ); - } + ); + } - #[test] - fn serialize_to_json() { - let config = test_config(); - let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()]; - println!("{}", serde_json::to_string_pretty(netconfig).unwrap()); - assert_eq!( - serde_json::to_string_pretty(netconfig).unwrap(), - r#"{ + #[test] + fn serialize_to_json() { + let config = test_config(); + let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()]; + println!("{}", serde_json::to_string_pretty(netconfig).unwrap()); + assert_eq!( + serde_json::to_string_pretty(netconfig).unwrap(), + r#"{ "version": 1, "selected_nymd_url": null, "selected_api_url": "https://my_api_url.com/", @@ -514,53 +511,53 @@ api_url = 'https://baz/api' } ] }"# - ); - } + ); + } - #[test] - fn serialize_and_deserialize_to_toml() { - let config = test_config(); - let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()]; - let config_str = toml::to_string_pretty(netconfig).unwrap(); - let config_from_toml: NetworkConfig = toml::from_str(&config_str).unwrap(); - assert_eq!(netconfig, &config_from_toml); - } + #[test] + fn serialize_and_deserialize_to_toml() { + let config = test_config(); + let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()]; + let config_str = toml::to_string_pretty(netconfig).unwrap(); + let config_from_toml: NetworkConfig = toml::from_str(&config_str).unwrap(); + assert_eq!(netconfig, &config_from_toml); + } - #[test] - fn get_urls_parsed_from_config() { - let config = test_config(); + #[test] + fn get_urls_parsed_from_config() { + let config = test_config(); - let nymd_url = config - .get_configured_validators(WalletNetwork::MAINNET) - .next() - .map(|v| v.nymd_url) - .unwrap(); - assert_eq!(nymd_url.as_ref(), "https://foo/"); + let nymd_url = config + .get_configured_validators(WalletNetwork::MAINNET) + .next() + .map(|v| v.nymd_url) + .unwrap(); + assert_eq!(nymd_url.as_ref(), "https://foo/"); - // The first entry is missing an API URL - let api_url = config - .get_configured_validators(WalletNetwork::MAINNET) - .next() - .and_then(|v| v.api_url); - assert_eq!(api_url, None); - } + // The first entry is missing an API URL + let api_url = config + .get_configured_validators(WalletNetwork::MAINNET) + .next() + .and_then(|v| v.api_url); + assert_eq!(api_url, None); + } - #[test] - fn get_urls_from_defaults() { - let config = Config::default(); + #[test] + fn get_urls_from_defaults() { + let config = Config::default(); - let nymd_url = config - .get_base_validators(WalletNetwork::MAINNET) - .next() - .map(|v| v.nymd_url) - .unwrap(); - assert_eq!(nymd_url.as_ref(), "https://rpc.nyx.nodes.guru/"); + let nymd_url = config + .get_base_validators(WalletNetwork::MAINNET) + .next() + .map(|v| v.nymd_url) + .unwrap(); + assert_eq!(nymd_url.as_ref(), "https://rpc.nyx.nodes.guru/"); - let api_url = config - .get_base_validators(WalletNetwork::MAINNET) - .next() - .and_then(|v| v.api_url) - .unwrap(); - assert_eq!(api_url.as_ref(), "https://validator.nymtech.net/api/",); - } + let api_url = config + .get_base_validators(WalletNetwork::MAINNET) + .next() + .and_then(|v| v.api_url) + .unwrap(); + assert_eq!(api_url.as_ref(), "https://validator.nymtech.net/api/",); + } } diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 9ef1b56b4e..3f6181e008 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -6,118 +6,118 @@ use validator_client::{nymd::error::NymdError, ValidatorClientError}; #[derive(Error, Debug)] pub enum BackendError { - #[error("{source}")] - Bip39Error { - #[from] - source: bip39::Error, - }, - #[error("{source}")] - TendermintError { - #[from] - source: tendermint_rpc::Error, - }, - #[error("{source}")] - NymdError { - #[from] - source: NymdError, - }, - #[error("{source}")] - CosmwasmStd { - #[from] - source: cosmwasm_std::StdError, - }, - #[error("{source}")] - ErrorReport { - #[from] - source: eyre::Report, - }, - #[error("{source}")] - ValidatorApiError { - #[from] - source: ValidatorAPIError, - }, - #[error("{source}")] - KeyDerivationError { - #[from] - source: argon2::Error, - }, - #[error("{source}")] - IOError { - #[from] - source: io::Error, - }, - #[error("{source}")] - SerdeJsonError { - #[from] - source: serde_json::Error, - }, - #[error("{source}")] - MalformedUrlProvided { - #[from] - source: url::ParseError, - }, - #[error("{source}")] - ReqwestError { - #[from] - source: reqwest::Error, - }, - #[error("failed to encrypt the given data with the provided password")] - EncryptionError, - #[error("failed to decrypt the given data with the provided password")] - DecryptionError, - #[error("Client has not been initialized yet, connect with mnemonic to initialize")] - ClientNotInitialized, - #[error("No balance available for address {0}")] - NoBalance(String), - #[error("{0} is not a valid denomination string")] - InvalidDenom(String), - #[error("{0} is not a valid network denomination string")] - InvalidNetworkDenom(String), - #[error("The provided network is not supported (yet)")] - NetworkNotSupported(config::defaults::all::Network), - #[error("Could not access the local data storage directory")] - UnknownStorageDirectory, - #[error("No validator API URL configured")] - NoValidatorApiUrlConfigured, - #[error("The wallet file already exists")] - WalletFileAlreadyExists, - #[error("The wallet file is not found")] - WalletFileNotFound, - #[error("Login ID not found in wallet")] - WalletNoSuchLoginId, - #[error("Account ID not found in wallet login")] - WalletNoSuchAccountIdInWalletLogin, - #[error("Login ID already found in wallet")] - WalletLoginIdAlreadyExists, - #[error("Account ID already found in wallet login")] - WalletAccountIdAlreadyExistsInWalletLogin, - #[error("Mnemonic already found in wallet login, was it already imported?")] - WalletMnemonicAlreadyExistsInWalletLogin, - #[error("Adding a different password to the wallet not currently supported")] - WalletDifferentPasswordDetected, - #[error("Unexpted mnemonic account for login")] - WalletUnexpectedMnemonicAccount, - #[error("Unexpted multiple account entry for login")] - WalletUnexpectedMultipleAccounts, - #[error("Failed to derive address from mnemonic")] - FailedToDeriveAddress, + #[error("{source}")] + Bip39Error { + #[from] + source: bip39::Error, + }, + #[error("{source}")] + TendermintError { + #[from] + source: tendermint_rpc::Error, + }, + #[error("{source}")] + NymdError { + #[from] + source: NymdError, + }, + #[error("{source}")] + CosmwasmStd { + #[from] + source: cosmwasm_std::StdError, + }, + #[error("{source}")] + ErrorReport { + #[from] + source: eyre::Report, + }, + #[error("{source}")] + ValidatorApiError { + #[from] + source: ValidatorAPIError, + }, + #[error("{source}")] + KeyDerivationError { + #[from] + source: argon2::Error, + }, + #[error("{source}")] + IOError { + #[from] + source: io::Error, + }, + #[error("{source}")] + SerdeJsonError { + #[from] + source: serde_json::Error, + }, + #[error("{source}")] + MalformedUrlProvided { + #[from] + source: url::ParseError, + }, + #[error("{source}")] + ReqwestError { + #[from] + source: reqwest::Error, + }, + #[error("failed to encrypt the given data with the provided password")] + EncryptionError, + #[error("failed to decrypt the given data with the provided password")] + DecryptionError, + #[error("Client has not been initialized yet, connect with mnemonic to initialize")] + ClientNotInitialized, + #[error("No balance available for address {0}")] + NoBalance(String), + #[error("{0} is not a valid denomination string")] + InvalidDenom(String), + #[error("{0} is not a valid network denomination string")] + InvalidNetworkDenom(String), + #[error("The provided network is not supported (yet)")] + NetworkNotSupported(config::defaults::all::Network), + #[error("Could not access the local data storage directory")] + UnknownStorageDirectory, + #[error("No validator API URL configured")] + NoValidatorApiUrlConfigured, + #[error("The wallet file already exists")] + WalletFileAlreadyExists, + #[error("The wallet file is not found")] + WalletFileNotFound, + #[error("Login ID not found in wallet")] + WalletNoSuchLoginId, + #[error("Account ID not found in wallet login")] + WalletNoSuchAccountIdInWalletLogin, + #[error("Login ID already found in wallet")] + WalletLoginIdAlreadyExists, + #[error("Account ID already found in wallet login")] + WalletAccountIdAlreadyExistsInWalletLogin, + #[error("Mnemonic already found in wallet login, was it already imported?")] + WalletMnemonicAlreadyExistsInWalletLogin, + #[error("Adding a different password to the wallet not currently supported")] + WalletDifferentPasswordDetected, + #[error("Unexpted mnemonic account for login")] + WalletUnexpectedMnemonicAccount, + #[error("Unexpted multiple account entry for login")] + WalletUnexpectedMultipleAccounts, + #[error("Failed to derive address from mnemonic")] + FailedToDeriveAddress, } impl Serialize for BackendError { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.collect_str(self) - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } } impl From for BackendError { - fn from(e: ValidatorClientError) -> Self { - match e { - ValidatorClientError::ValidatorAPIError { source } => source.into(), - ValidatorClientError::MalformedUrlProvided(e) => e.into(), - ValidatorClientError::NymdError(e) => e.into(), + fn from(e: ValidatorClientError) -> Self { + match e { + ValidatorClientError::ValidatorAPIError { source } => source.into(), + ValidatorClientError::MalformedUrlProvided(e) => e.into(), + ValidatorClientError::NymdError(e) => e.into(), + } } - } } diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 2b59a437fa..91e3734cdf 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -1,6 +1,6 @@ #![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" )] use crate::menu::AddDefaultSubmenus; @@ -24,128 +24,128 @@ mod utils; mod wallet_storage; fn main() { - dotenv::dotenv().ok(); - setup_logging(); + dotenv::dotenv().ok(); + setup_logging(); - tauri::Builder::default() - .manage(Arc::new(RwLock::new(State::default()))) - .invoke_handler(tauri::generate_handler![ - mixnet::account::add_account_for_password, - mixnet::account::connect_with_mnemonic, - mixnet::account::create_new_account, - mixnet::account::create_new_mnemonic, - mixnet::account::create_password, - mixnet::account::does_password_file_exist, - mixnet::account::get_balance, - mixnet::account::list_accounts, - mixnet::account::logout, - mixnet::account::remove_account_for_password, - mixnet::account::remove_password, - mixnet::account::show_mnemonic_for_account_in_password, - mixnet::account::sign_in_with_password, - mixnet::account::sign_in_with_password_and_account_id, - mixnet::account::switch_network, - mixnet::account::validate_mnemonic, - mixnet::admin::get_contract_settings, - mixnet::admin::update_contract_settings, - mixnet::bond::bond_gateway, - mixnet::bond::bond_mixnode, - mixnet::bond::gateway_bond_details, - mixnet::bond::get_operator_rewards, - mixnet::bond::mixnode_bond_details, - mixnet::bond::unbond_gateway, - mixnet::bond::unbond_mixnode, - mixnet::bond::update_mixnode, - mixnet::delegate::delegate_to_mixnode, - mixnet::delegate::get_delegator_rewards, - mixnet::delegate::get_pending_delegation_events, - mixnet::delegate::get_reverse_mix_delegations_paged, - mixnet::delegate::undelegate_from_mixnode, - mixnet::epoch::get_current_epoch, - mixnet::send::send, - network_config::add_validator, - network_config::get_validator_api_urls, - network_config::get_validator_nymd_urls, - network_config::remove_validator, - network_config::select_validator_api_url, - network_config::select_validator_nymd_url, - network_config::update_validator_urls, - state::load_config_from_files, - state::save_config_to_files, - utils::major_to_minor, - utils::minor_to_major, - utils::owns_gateway, - utils::owns_mixnode, - utils::get_env, - validator_api::status::gateway_core_node_status, - validator_api::status::mixnode_core_node_status, - validator_api::status::mixnode_inclusion_probability, - validator_api::status::mixnode_reward_estimation, - validator_api::status::mixnode_stake_saturation, - validator_api::status::mixnode_status, - vesting::bond::vesting_bond_gateway, - vesting::bond::vesting_bond_mixnode, - vesting::bond::vesting_unbond_gateway, - vesting::bond::vesting_unbond_mixnode, - vesting::bond::vesting_update_mixnode, - vesting::bond::withdraw_vested_coins, - vesting::delegate::get_pending_vesting_delegation_events, - vesting::delegate::vesting_delegate_to_mixnode, - vesting::delegate::vesting_undelegate_from_mixnode, - vesting::queries::delegated_free, - vesting::queries::delegated_vesting, - vesting::queries::get_account_info, - vesting::queries::get_current_vesting_period, - vesting::queries::locked_coins, - vesting::queries::original_vesting, - vesting::queries::spendable_coins, - vesting::queries::vested_coins, - vesting::queries::vesting_coins, - vesting::queries::vesting_end_time, - vesting::queries::vesting_get_gateway_pledge, - vesting::queries::vesting_get_mixnode_pledge, - vesting::queries::vesting_start_time, - // simulate endpoints - simulate::admin::simulate_update_contract_settings, - simulate::mixnet::simulate_bond_gateway, - simulate::mixnet::simulate_bond_mixnode, - simulate::mixnet::simulate_unbond_gateway, - simulate::mixnet::simulate_unbond_mixnode, - simulate::mixnet::simulate_update_mixnode, - simulate::mixnet::simulate_delegate_to_mixnode, - simulate::mixnet::simulate_undelegate_from_mixnode, - simulate::cosmos::simulate_send, - simulate::vesting::simulate_vesting_bond_gateway, - simulate::vesting::simulate_vesting_bond_mixnode, - simulate::vesting::simulate_vesting_unbond_gateway, - simulate::vesting::simulate_vesting_unbond_mixnode, - simulate::vesting::simulate_vesting_update_mixnode, - simulate::vesting::simulate_withdraw_vested_coins, - ]) - .menu(Menu::new().add_default_app_submenu_if_macos()) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + tauri::Builder::default() + .manage(Arc::new(RwLock::new(State::default()))) + .invoke_handler(tauri::generate_handler![ + mixnet::account::add_account_for_password, + mixnet::account::connect_with_mnemonic, + mixnet::account::create_new_account, + mixnet::account::create_new_mnemonic, + mixnet::account::create_password, + mixnet::account::does_password_file_exist, + mixnet::account::get_balance, + mixnet::account::list_accounts, + mixnet::account::logout, + mixnet::account::remove_account_for_password, + mixnet::account::remove_password, + mixnet::account::show_mnemonic_for_account_in_password, + mixnet::account::sign_in_with_password, + mixnet::account::sign_in_with_password_and_account_id, + mixnet::account::switch_network, + mixnet::account::validate_mnemonic, + mixnet::admin::get_contract_settings, + mixnet::admin::update_contract_settings, + mixnet::bond::bond_gateway, + mixnet::bond::bond_mixnode, + mixnet::bond::gateway_bond_details, + mixnet::bond::get_operator_rewards, + mixnet::bond::mixnode_bond_details, + mixnet::bond::unbond_gateway, + mixnet::bond::unbond_mixnode, + mixnet::bond::update_mixnode, + mixnet::delegate::delegate_to_mixnode, + mixnet::delegate::get_delegator_rewards, + mixnet::delegate::get_pending_delegation_events, + mixnet::delegate::get_reverse_mix_delegations_paged, + mixnet::delegate::undelegate_from_mixnode, + mixnet::epoch::get_current_epoch, + mixnet::send::send, + network_config::add_validator, + network_config::get_validator_api_urls, + network_config::get_validator_nymd_urls, + network_config::remove_validator, + network_config::select_validator_api_url, + network_config::select_validator_nymd_url, + network_config::update_validator_urls, + state::load_config_from_files, + state::save_config_to_files, + utils::major_to_minor, + utils::minor_to_major, + utils::owns_gateway, + utils::owns_mixnode, + utils::get_env, + validator_api::status::gateway_core_node_status, + validator_api::status::mixnode_core_node_status, + validator_api::status::mixnode_inclusion_probability, + validator_api::status::mixnode_reward_estimation, + validator_api::status::mixnode_stake_saturation, + validator_api::status::mixnode_status, + vesting::bond::vesting_bond_gateway, + vesting::bond::vesting_bond_mixnode, + vesting::bond::vesting_unbond_gateway, + vesting::bond::vesting_unbond_mixnode, + vesting::bond::vesting_update_mixnode, + vesting::bond::withdraw_vested_coins, + vesting::delegate::get_pending_vesting_delegation_events, + vesting::delegate::vesting_delegate_to_mixnode, + vesting::delegate::vesting_undelegate_from_mixnode, + vesting::queries::delegated_free, + vesting::queries::delegated_vesting, + vesting::queries::get_account_info, + vesting::queries::get_current_vesting_period, + vesting::queries::locked_coins, + vesting::queries::original_vesting, + vesting::queries::spendable_coins, + vesting::queries::vested_coins, + vesting::queries::vesting_coins, + vesting::queries::vesting_end_time, + vesting::queries::vesting_get_gateway_pledge, + vesting::queries::vesting_get_mixnode_pledge, + vesting::queries::vesting_start_time, + // simulate endpoints + simulate::admin::simulate_update_contract_settings, + simulate::mixnet::simulate_bond_gateway, + simulate::mixnet::simulate_bond_mixnode, + simulate::mixnet::simulate_unbond_gateway, + simulate::mixnet::simulate_unbond_mixnode, + simulate::mixnet::simulate_update_mixnode, + simulate::mixnet::simulate_delegate_to_mixnode, + simulate::mixnet::simulate_undelegate_from_mixnode, + simulate::cosmos::simulate_send, + simulate::vesting::simulate_vesting_bond_gateway, + simulate::vesting::simulate_vesting_bond_mixnode, + simulate::vesting::simulate_vesting_unbond_gateway, + simulate::vesting::simulate_vesting_unbond_mixnode, + simulate::vesting::simulate_vesting_update_mixnode, + simulate::vesting::simulate_withdraw_vested_coins, + ]) + .menu(Menu::new().add_default_app_submenu_if_macos()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } fn setup_logging() { - let mut log_builder = pretty_env_logger::formatted_timed_builder(); - if let Ok(s) = ::std::env::var("RUST_LOG") { - log_builder.parse_filters(&s); - } else { - // default to 'Info' - log_builder.filter(None, log::LevelFilter::Info); - } + let mut log_builder = pretty_env_logger::formatted_timed_builder(); + if let Ok(s) = ::std::env::var("RUST_LOG") { + log_builder.parse_filters(&s); + } else { + // default to 'Info' + log_builder.filter(None, log::LevelFilter::Info); + } - log_builder - .filter_module("hyper", log::LevelFilter::Warn) - .filter_module("tokio_reactor", log::LevelFilter::Warn) - .filter_module("reqwest", log::LevelFilter::Warn) - .filter_module("mio", log::LevelFilter::Warn) - .filter_module("want", log::LevelFilter::Warn) - .filter_module("sled", log::LevelFilter::Warn) - .filter_module("tungstenite", log::LevelFilter::Warn) - .filter_module("tokio_tungstenite", log::LevelFilter::Warn) - .filter_module("rustls", log::LevelFilter::Warn) - .filter_module("tokio_util", log::LevelFilter::Warn) - .init(); + log_builder + .filter_module("hyper", log::LevelFilter::Warn) + .filter_module("tokio_reactor", log::LevelFilter::Warn) + .filter_module("reqwest", log::LevelFilter::Warn) + .filter_module("mio", log::LevelFilter::Warn) + .filter_module("want", log::LevelFilter::Warn) + .filter_module("sled", log::LevelFilter::Warn) + .filter_module("tungstenite", log::LevelFilter::Warn) + .filter_module("tokio_tungstenite", log::LevelFilter::Warn) + .filter_module("rustls", log::LevelFilter::Warn) + .filter_module("tokio_util", log::LevelFilter::Warn) + .init(); } diff --git a/nym-wallet/src-tauri/src/menu.rs b/nym-wallet/src-tauri/src/menu.rs index 493ed6ceb9..6c7a8d4bd7 100644 --- a/nym-wallet/src-tauri/src/menu.rs +++ b/nym-wallet/src-tauri/src/menu.rs @@ -3,25 +3,25 @@ use tauri::Menu; use tauri::{MenuItem, Submenu}; pub trait AddDefaultSubmenus { - fn add_default_app_submenu_if_macos(self) -> Self; + fn add_default_app_submenu_if_macos(self) -> Self; } impl AddDefaultSubmenus for Menu { - fn add_default_app_submenu_if_macos(self) -> Menu { - #[cfg(target_os = "macos")] - return self.add_submenu(Submenu::new( - "Menu", - Menu::new() - .add_native_item(MenuItem::Copy) - .add_native_item(MenuItem::Cut) - .add_native_item(MenuItem::Paste) - .add_native_item(MenuItem::Hide) - .add_native_item(MenuItem::HideOthers) - .add_native_item(MenuItem::SelectAll) - .add_native_item(MenuItem::ShowAll) - .add_native_item(MenuItem::Quit), - )); - #[cfg(not(target_os = "macos"))] - return self; - } + fn add_default_app_submenu_if_macos(self) -> Menu { + #[cfg(target_os = "macos")] + return self.add_submenu(Submenu::new( + "Menu", + Menu::new() + .add_native_item(MenuItem::Copy) + .add_native_item(MenuItem::Cut) + .add_native_item(MenuItem::Paste) + .add_native_item(MenuItem::Hide) + .add_native_item(MenuItem::HideOthers) + .add_native_item(MenuItem::SelectAll) + .add_native_item(MenuItem::ShowAll) + .add_native_item(MenuItem::Quit), + )); + #[cfg(not(target_os = "macos"))] + return self; + } } diff --git a/nym-wallet/src-tauri/src/network.rs b/nym-wallet/src-tauri/src/network.rs index 6ab4098427..eae5058519 100644 --- a/nym-wallet/src-tauri/src/network.rs +++ b/nym-wallet/src-tauri/src/network.rs @@ -15,54 +15,54 @@ use validator_client::nymd::Denom; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/network.ts"))] #[derive(Copy, Clone, Debug, Deserialize, EnumIter, Eq, Hash, PartialEq, Serialize)] pub enum Network { - QA, - SANDBOX, - MAINNET, + QA, + SANDBOX, + MAINNET, } impl Network { - pub fn as_key(&self) -> String { - self.to_string().to_lowercase() - } - - pub fn denom(&self) -> Denom { - match self { - // network defaults should be correctly formatted - Network::QA => Denom::from_str(qa::DENOM).unwrap(), - Network::SANDBOX => Denom::from_str(sandbox::DENOM).unwrap(), - Network::MAINNET => Denom::from_str(mainnet::DENOM).unwrap(), + pub fn as_key(&self) -> String { + self.to_string().to_lowercase() + } + + pub fn denom(&self) -> Denom { + match self { + // network defaults should be correctly formatted + Network::QA => Denom::from_str(qa::DENOM).unwrap(), + Network::SANDBOX => Denom::from_str(sandbox::DENOM).unwrap(), + Network::MAINNET => Denom::from_str(mainnet::DENOM).unwrap(), + } } - } } impl Default for Network { - fn default() -> Self { - Network::MAINNET - } + fn default() -> Self { + Network::MAINNET + } } impl fmt::Display for Network { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } } impl From for Network { - fn from(network: ConfigNetwork) -> Self { - match network { - ConfigNetwork::QA => Network::QA, - ConfigNetwork::SANDBOX => Network::SANDBOX, - ConfigNetwork::MAINNET => Network::MAINNET, + fn from(network: ConfigNetwork) -> Self { + match network { + ConfigNetwork::QA => Network::QA, + ConfigNetwork::SANDBOX => Network::SANDBOX, + ConfigNetwork::MAINNET => Network::MAINNET, + } } - } } impl From for ConfigNetwork { - fn from(network: Network) -> Self { - match network { - Network::QA => ConfigNetwork::QA, - Network::SANDBOX => ConfigNetwork::SANDBOX, - Network::MAINNET => ConfigNetwork::MAINNET, + fn from(network: Network) -> Self { + match network { + Network::QA => ConfigNetwork::QA, + Network::SANDBOX => ConfigNetwork::SANDBOX, + Network::MAINNET => ConfigNetwork::MAINNET, + } } - } } diff --git a/nym-wallet/src-tauri/src/network_config.rs b/nym-wallet/src-tauri/src/network_config.rs index 91ee051749..75a8e38eec 100644 --- a/nym-wallet/src-tauri/src/network_config.rs +++ b/nym-wallet/src-tauri/src/network_config.rs @@ -14,15 +14,15 @@ use tokio::sync::RwLock; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))] #[derive(Debug, Serialize, Deserialize)] pub struct ValidatorUrls { - pub urls: Vec, + pub urls: Vec, } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurl.ts"))] #[derive(Debug, Serialize, Deserialize)] pub struct ValidatorUrl { - pub url: String, - pub name: Option, + pub url: String, + pub name: Option, } // The type used when adding or removing validators, effectively the input. @@ -31,98 +31,98 @@ pub struct ValidatorUrl { #[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))] #[derive(Debug, Serialize, Deserialize)] pub struct Validator { - pub nymd_url: String, - pub nymd_name: Option, - pub api_url: Option, + pub nymd_url: String, + pub nymd_name: Option, + pub api_url: Option, } impl fmt::Display for Validator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let nymd_url = format!("nymd_url: {}", self.nymd_url); - let api_url = self - .api_url - .as_ref() - .map(|api_url| format!(", api_url: {}", api_url)) - .unwrap_or_default(); - write!(f, "{nymd_url}{api_url}") - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let nymd_url = format!("nymd_url: {}", self.nymd_url); + let api_url = self + .api_url + .as_ref() + .map(|api_url| format!(", api_url: {}", api_url)) + .unwrap_or_default(); + write!(f, "{nymd_url}{api_url}") + } } #[tauri::command] pub async fn get_validator_nymd_urls( - network: WalletNetwork, - state: tauri::State<'_, Arc>>, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, ) -> Result { - let state = state.read().await; - let urls: Vec = state.get_nymd_urls(network).collect(); - Ok(ValidatorUrls { urls }) + let state = state.read().await; + let urls: Vec = state.get_nymd_urls(network).collect(); + Ok(ValidatorUrls { urls }) } #[tauri::command] pub async fn get_validator_api_urls( - network: WalletNetwork, - state: tauri::State<'_, Arc>>, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, ) -> Result { - let state = state.read().await; - let urls: Vec = state.get_api_urls(network).collect(); - Ok(ValidatorUrls { urls }) + let state = state.read().await; + let urls: Vec = state.get_api_urls(network).collect(); + Ok(ValidatorUrls { urls }) } #[tauri::command] pub async fn select_validator_nymd_url( - url: &str, - network: WalletNetwork, - state: tauri::State<'_, Arc>>, + url: &str, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - log::debug!("Selecting new validator nymd_url for {network}: {url}"); - state - .write() - .await - .select_validator_nymd_url(url, network)?; - Ok(()) + log::debug!("Selecting new validator nymd_url for {network}: {url}"); + state + .write() + .await + .select_validator_nymd_url(url, network)?; + Ok(()) } #[tauri::command] pub async fn select_validator_api_url( - url: &str, - network: WalletNetwork, - state: tauri::State<'_, Arc>>, + url: &str, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - log::debug!("Selecting new validator api_url for {network}: {url}"); - state.write().await.select_validator_api_url(url, network)?; - Ok(()) + log::debug!("Selecting new validator api_url for {network}: {url}"); + state.write().await.select_validator_api_url(url, network)?; + Ok(()) } #[tauri::command] pub async fn add_validator( - validator: Validator, - network: WalletNetwork, - state: tauri::State<'_, Arc>>, + validator: Validator, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - log::debug!("Add validator for {network}: {validator}"); - let url = validator.try_into()?; - state.write().await.add_validator_url(url, network); - Ok(()) + log::debug!("Add validator for {network}: {validator}"); + let url = validator.try_into()?; + state.write().await.add_validator_url(url, network); + Ok(()) } #[tauri::command] pub async fn remove_validator( - validator: Validator, - network: WalletNetwork, - state: tauri::State<'_, Arc>>, + validator: Validator, + network: WalletNetwork, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - log::debug!("Remove validator for {network}: {validator}"); - let url = validator.try_into()?; - state.write().await.remove_validator_url(url, network); - Ok(()) + log::debug!("Remove validator for {network}: {validator}"); + let url = validator.try_into()?; + state.write().await.remove_validator_url(url, network); + Ok(()) } // Update the list of validators by fecthing additional ones remotely. If it fails, just ignore. #[tauri::command] pub async fn update_validator_urls( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let mut w_state = state.write().await; - let _r = w_state.fetch_updated_validator_urls().await; - Ok(()) + let mut w_state = state.write().await; + let _r = w_state.fetch_updated_validator_urls().await; + Ok(()) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 3e70d82fbb..a84ea2eff4 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -29,659 +29,663 @@ use validator_client::Client; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/account.ts"))] #[derive(Serialize, Deserialize)] pub struct Account { - contract_address: String, - client_address: String, - denom: Denom, + contract_address: String, + client_address: String, + denom: Denom, } impl Account { - pub fn new(contract_address: String, client_address: String, denom: Denom) -> Self { - Account { - contract_address, - client_address, - denom, + pub fn new(contract_address: String, client_address: String, denom: Denom) -> Self { + Account { + contract_address, + client_address, + denom, + } } - } } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/createdaccount.ts"))] #[derive(Serialize, Deserialize)] pub struct CreatedAccount { - account: Account, - mnemonic: String, + account: Account, + mnemonic: String, } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/createdaccount.ts"))] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AccountEntry { - id: String, - address: String, + id: String, + address: String, } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/balance.ts"))] #[derive(Serialize, Deserialize)] pub struct Balance { - coin: Coin, - printable_balance: String, + coin: Coin, + printable_balance: String, } #[tauri::command] pub async fn connect_with_mnemonic( - mnemonic: String, - state: tauri::State<'_, Arc>>, + mnemonic: String, + state: tauri::State<'_, Arc>>, ) -> Result { - let mnemonic = Mnemonic::from_str(&mnemonic)?; - _connect_with_mnemonic(mnemonic, state).await + let mnemonic = Mnemonic::from_str(&mnemonic)?; + _connect_with_mnemonic(mnemonic, state).await } #[tauri::command] pub async fn get_balance( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - let denom = state.read().await.current_network().denom(); - match nymd_client!(state) - .get_balance(nymd_client!(state).address(), denom.clone()) - .await - { - Ok(Some(coin)) => { - let coin = Coin::new( - &coin.amount.to_string(), - &Denom::from_str(&coin.denom.to_string())?, - ); - Ok(Balance { - coin: coin.clone(), - printable_balance: format!("{} {}", coin.to_major().amount(), &denom.as_ref()[1..]), - }) + let denom = state.read().await.current_network().denom(); + match nymd_client!(state) + .get_balance(nymd_client!(state).address(), denom.clone()) + .await + { + Ok(Some(coin)) => { + let coin = Coin::new( + &coin.amount.to_string(), + &Denom::from_str(&coin.denom.to_string())?, + ); + Ok(Balance { + coin: coin.clone(), + printable_balance: format!("{} {}", coin.to_major().amount(), &denom.as_ref()[1..]), + }) + } + Ok(None) => Err(BackendError::NoBalance( + nymd_client!(state).address().to_string(), + )), + Err(e) => Err(BackendError::from(e)), } - Ok(None) => Err(BackendError::NoBalance( - nymd_client!(state).address().to_string(), - )), - Err(e) => Err(BackendError::from(e)), - } } #[tauri::command] pub async fn create_new_account( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - let rand_mnemonic = random_mnemonic(); - let account = connect_with_mnemonic(rand_mnemonic.to_string(), state).await?; - Ok(CreatedAccount { - account, - mnemonic: rand_mnemonic.to_string(), - }) + let rand_mnemonic = random_mnemonic(); + let account = connect_with_mnemonic(rand_mnemonic.to_string(), state).await?; + Ok(CreatedAccount { + account, + mnemonic: rand_mnemonic.to_string(), + }) } #[tauri::command] pub fn create_new_mnemonic() -> String { - random_mnemonic().to_string() + random_mnemonic().to_string() } #[tauri::command] pub fn validate_mnemonic(mnemonic: &str) -> bool { - Mnemonic::from_str(mnemonic).is_ok() + Mnemonic::from_str(mnemonic).is_ok() } #[tauri::command] pub async fn switch_network( - state: tauri::State<'_, Arc>>, - network: WalletNetwork, + state: tauri::State<'_, Arc>>, + network: WalletNetwork, ) -> Result { - let account = { - let r_state = state.read().await; - let client = r_state.client(network)?; - let denom = network.denom(); + let account = { + let r_state = state.read().await; + let client = r_state.client(network)?; + let denom = network.denom(); - Account::new( - client.nymd.mixnet_contract_address()?.to_string(), - client.nymd.address().to_string(), - denom.try_into()?, - ) - }; + Account::new( + client.nymd.mixnet_contract_address()?.to_string(), + client.nymd.address().to_string(), + denom.try_into()?, + ) + }; - let mut w_state = state.write().await; - w_state.set_network(network); + let mut w_state = state.write().await; + w_state.set_network(network); - Ok(account) + Ok(account) } #[tauri::command] pub async fn logout(state: tauri::State<'_, Arc>>) -> Result<(), BackendError> { - state.write().await.logout(); - Ok(()) + state.write().await.logout(); + Ok(()) } fn random_mnemonic() -> Mnemonic { - let mut rng = rand::thread_rng(); - Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() + let mut rng = rand::thread_rng(); + Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() } async fn _connect_with_mnemonic( - mnemonic: Mnemonic, - state: tauri::State<'_, Arc>>, + mnemonic: Mnemonic, + state: tauri::State<'_, Arc>>, ) -> Result { - { - let mut w_state = state.write().await; - w_state.load_config_files(); - } - - network_config::update_validator_urls(state.clone()).await?; - - let config = { - let state = state.read().await; - - // Take the oppertunity to list all the known validators while we have the state. - for network in WalletNetwork::iter() { - log::debug!( - "List of validators for {network}: [\n{}\n]", - state.get_config_validator_entries(network).format(",\n") - ); + { + let mut w_state = state.write().await; + w_state.load_config_files(); } - state.config().clone() - }; + network_config::update_validator_urls(state.clone()).await?; - // Get all the urls needed for the connection test - let (untested_nymd_urls, untested_api_urls) = { - let state = state.read().await; - (state.get_all_nymd_urls(), state.get_all_api_urls()) - }; - let default_nymd_urls: HashMap = untested_nymd_urls - .iter() - .map(|(network, urls)| (*network, urls.iter().next().unwrap().clone())) - .collect(); - let default_api_urls: HashMap = untested_api_urls - .iter() - .map(|(network, urls)| (*network, urls.iter().next().unwrap().clone())) - .collect(); + let config = { + let state = state.read().await; - // Run connection tests on all nymd and validator-api endpoints - let (nymd_urls, api_urls) = - run_connection_test(untested_nymd_urls, untested_api_urls, &config).await; + // Take the oppertunity to list all the known validators while we have the state. + for network in WalletNetwork::iter() { + log::debug!( + "List of validators for {network}: [\n{}\n]", + state.get_config_validator_entries(network).format(",\n") + ); + } - // Create clients for all networks - let clients = create_clients( - &nymd_urls, - &api_urls, - &default_nymd_urls, - &default_api_urls, - &config, - &mnemonic, - )?; + state.config().clone() + }; - // Set the default account - let default_network: WalletNetwork = config::defaults::DEFAULT_NETWORK.into(); - let client_for_default_network = clients - .iter() - .find(|client| WalletNetwork::from(client.network) == default_network); - let account_for_default_network = match client_for_default_network { - Some(client) => Ok(Account::new( - client.nymd.mixnet_contract_address()?.to_string(), - client.nymd.address().to_string(), - default_network.denom().try_into()?, - )), - None => Err(BackendError::NetworkNotSupported( - config::defaults::DEFAULT_NETWORK, - )), - }; + // Get all the urls needed for the connection test + let (untested_nymd_urls, untested_api_urls) = { + let state = state.read().await; + (state.get_all_nymd_urls(), state.get_all_api_urls()) + }; + let default_nymd_urls: HashMap = untested_nymd_urls + .iter() + .map(|(network, urls)| (*network, urls.iter().next().unwrap().clone())) + .collect(); + let default_api_urls: HashMap = untested_api_urls + .iter() + .map(|(network, urls)| (*network, urls.iter().next().unwrap().clone())) + .collect(); - // Register all the clients - { - let mut w_state = state.write().await; - w_state.logout(); - } - for client in clients { - let network: WalletNetwork = client.network.into(); - let mut w_state = state.write().await; - w_state.add_client(network, client); - } + // Run connection tests on all nymd and validator-api endpoints + let (nymd_urls, api_urls) = + run_connection_test(untested_nymd_urls, untested_api_urls, &config).await; - account_for_default_network + // Create clients for all networks + let clients = create_clients( + &nymd_urls, + &api_urls, + &default_nymd_urls, + &default_api_urls, + &config, + &mnemonic, + )?; + + // Set the default account + let default_network: WalletNetwork = config::defaults::DEFAULT_NETWORK.into(); + let client_for_default_network = clients + .iter() + .find(|client| WalletNetwork::from(client.network) == default_network); + let account_for_default_network = match client_for_default_network { + Some(client) => Ok(Account::new( + client.nymd.mixnet_contract_address()?.to_string(), + client.nymd.address().to_string(), + default_network.denom().try_into()?, + )), + None => Err(BackendError::NetworkNotSupported( + config::defaults::DEFAULT_NETWORK, + )), + }; + + // Register all the clients + { + let mut w_state = state.write().await; + w_state.logout(); + } + for client in clients { + let network: WalletNetwork = client.network.into(); + let mut w_state = state.write().await; + w_state.add_client(network, client); + } + + account_for_default_network } async fn run_connection_test( - untested_nymd_urls: HashMap>, - untested_api_urls: HashMap>, - config: &Config, + untested_nymd_urls: HashMap>, + untested_api_urls: HashMap>, + config: &Config, ) -> ( - HashMap>, - HashMap>, + HashMap>, + HashMap>, ) { - let mixnet_contract_address = WalletNetwork::iter() - .map(|network| (network.into(), config.get_mixnet_contract_address(network))) - .collect::>(); + let mixnet_contract_address = WalletNetwork::iter() + .map(|network| (network.into(), config.get_mixnet_contract_address(network))) + .collect::>(); - let untested_nymd_urls = untested_nymd_urls - .into_iter() - .flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url))); + let untested_nymd_urls = untested_nymd_urls + .into_iter() + .flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url))); - let untested_api_urls = untested_api_urls - .into_iter() - .flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url))); + let untested_api_urls = untested_api_urls + .into_iter() + .flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url))); - validator_client::connection_tester::run_validator_connection_test( - untested_nymd_urls, - untested_api_urls, - mixnet_contract_address, - ) - .await + validator_client::connection_tester::run_validator_connection_test( + untested_nymd_urls, + untested_api_urls, + mixnet_contract_address, + ) + .await } fn create_clients( - nymd_urls: &HashMap>, - api_urls: &HashMap>, - default_nymd_urls: &HashMap, - default_api_urls: &HashMap, - config: &Config, - mnemonic: &Mnemonic, + nymd_urls: &HashMap>, + api_urls: &HashMap>, + default_nymd_urls: &HashMap, + default_api_urls: &HashMap, + config: &Config, + mnemonic: &Mnemonic, ) -> Result>, BackendError> { - let mut clients = Vec::new(); - for network in WalletNetwork::iter() { - let nymd_url = if let Some(url) = config.get_selected_validator_nymd_url(network) { - log::debug!("Using selected nymd_url for {network}: {url}"); - url.clone() - } else { - let default_nymd_url = default_nymd_urls - .get(&network) - .expect("Expected at least one nymd_url"); - select_random_responding_url(nymd_urls, network).unwrap_or_else(|| { - log::debug!("No successful nymd_urls for {network}: using default: {default_nymd_url}"); - default_nymd_url.clone() - }) - }; + let mut clients = Vec::new(); + for network in WalletNetwork::iter() { + let nymd_url = if let Some(url) = config.get_selected_validator_nymd_url(network) { + log::debug!("Using selected nymd_url for {network}: {url}"); + url.clone() + } else { + let default_nymd_url = default_nymd_urls + .get(&network) + .expect("Expected at least one nymd_url"); + select_random_responding_url(nymd_urls, network).unwrap_or_else(|| { + log::debug!( + "No successful nymd_urls for {network}: using default: {default_nymd_url}" + ); + default_nymd_url.clone() + }) + }; - let api_url = if let Some(url) = config.get_selected_validator_api_url(&network) { - log::debug!("Using selected api_url for {network}: {url}"); - url.clone() - } else { - let default_api_url = default_api_urls - .get(&network) - .expect("Expected at least one api url"); - select_first_responding_url(api_urls, network).unwrap_or_else(|| { - log::debug!("No passing api_urls for {network}: using default: {default_api_url}"); - default_api_url.clone() - }) - }; + let api_url = if let Some(url) = config.get_selected_validator_api_url(&network) { + log::debug!("Using selected api_url for {network}: {url}"); + url.clone() + } else { + let default_api_url = default_api_urls + .get(&network) + .expect("Expected at least one api url"); + select_first_responding_url(api_urls, network).unwrap_or_else(|| { + log::debug!("No passing api_urls for {network}: using default: {default_api_url}"); + default_api_url.clone() + }) + }; - log::info!("Connecting to: nymd_url: {nymd_url} for {network}"); - log::info!("Connecting to: api_url: {api_url} for {network}"); + log::info!("Connecting to: nymd_url: {nymd_url} for {network}"); + log::info!("Connecting to: api_url: {api_url} for {network}"); - let mut client = validator_client::Client::new_signing( - validator_client::Config::new( - network.into(), - nymd_url, - api_url, - config.get_mixnet_contract_address(network), - config.get_vesting_contract_address(network), - config.get_bandwidth_claim_contract_address(network), - ), - mnemonic.clone(), - )?; - client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER); - clients.push(client); - } - Ok(clients) + let mut client = validator_client::Client::new_signing( + validator_client::Config::new( + network.into(), + nymd_url, + api_url, + config.get_mixnet_contract_address(network), + config.get_vesting_contract_address(network), + config.get_bandwidth_claim_contract_address(network), + ), + mnemonic.clone(), + )?; + client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER); + clients.push(client); + } + Ok(clients) } fn select_random_responding_url( - urls: &HashMap>, - network: WalletNetwork, + urls: &HashMap>, + network: WalletNetwork, ) -> Option { - urls.get(&network.into()).and_then(|urls| { - let urls: Vec<_> = urls - .iter() - .filter_map(|(url, result)| if *result { Some(url.clone()) } else { None }) - .collect(); - urls.choose(&mut rand::thread_rng()).cloned() - }) + urls.get(&network.into()).and_then(|urls| { + let urls: Vec<_> = urls + .iter() + .filter_map(|(url, result)| if *result { Some(url.clone()) } else { None }) + .collect(); + urls.choose(&mut rand::thread_rng()).cloned() + }) } fn select_first_responding_url( - urls: &HashMap>, - network: WalletNetwork, - //config: &Config, + urls: &HashMap>, + network: WalletNetwork, + //config: &Config, ) -> Option { - urls.get(&network.into()).and_then(|urls| { - urls - .iter() - .find_map(|(url, result)| if *result { Some(url.clone()) } else { None }) - }) + urls.get(&network.into()).and_then(|urls| { + urls.iter() + .find_map(|(url, result)| if *result { Some(url.clone()) } else { None }) + }) } #[tauri::command] pub fn does_password_file_exist() -> Result { - log::info!("Checking wallet file"); - let file = wallet_storage::wallet_login_filepath()?; - if file.exists() { - log::info!("Exists: {}", file.to_string_lossy()); - Ok(true) - } else { - log::info!("Does not exist: {}", file.to_string_lossy()); - Ok(false) - } + log::info!("Checking wallet file"); + let file = wallet_storage::wallet_login_filepath()?; + if file.exists() { + log::info!("Exists: {}", file.to_string_lossy()); + Ok(true) + } else { + log::info!("Does not exist: {}", file.to_string_lossy()); + Ok(false) + } } #[tauri::command] pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendError> { - if does_password_file_exist()? { - return Err(BackendError::WalletFileAlreadyExists); - } - log::info!("Creating password"); + if does_password_file_exist()? { + return Err(BackendError::WalletFileAlreadyExists); + } + log::info!("Creating password"); - let mnemonic = Mnemonic::from_str(mnemonic)?; - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - // Currently we only support a single, default, login id in the wallet - let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let password = wallet_storage::UserPassword::new(password); - wallet_storage::store_login_with_multiple_accounts(mnemonic, hd_path, login_id, &password) + let mnemonic = Mnemonic::from_str(mnemonic)?; + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + // Currently we only support a single, default, login id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let password = wallet_storage::UserPassword::new(password); + wallet_storage::store_login_with_multiple_accounts(mnemonic, hd_path, login_id, &password) } #[tauri::command] pub async fn sign_in_with_password( - password: String, - state: tauri::State<'_, Arc>>, + password: String, + state: tauri::State<'_, Arc>>, ) -> Result { - log::info!("Signing in with password"); + log::info!("Signing in with password"); - // Currently we only support a single, default, id in the wallet - let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let password = wallet_storage::UserPassword::new(password); - let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; + // Currently we only support a single, default, id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let password = wallet_storage::UserPassword::new(password); + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; - let mnemonic = extract_first_mnemonic(&stored_login)?; - let first_login_id_when_converting = login_id.into(); - set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()).await?; + let mnemonic = extract_first_mnemonic(&stored_login)?; + let first_login_id_when_converting = login_id.into(); + set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()) + .await?; - _connect_with_mnemonic(mnemonic, state).await + _connect_with_mnemonic(mnemonic, state).await } fn extract_first_mnemonic( - stored_login: &wallet_storage::StoredLogin, + stored_login: &wallet_storage::StoredLogin, ) -> Result { - let mnemonic = match stored_login { - wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), - wallet_storage::StoredLogin::Multiple(ref accounts) => { - // Login using the first account in the list - accounts - .get_accounts() - .next() - .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? - .mnemonic() - .clone() - } - }; + let mnemonic = match stored_login { + wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), + wallet_storage::StoredLogin::Multiple(ref accounts) => { + // Login using the first account in the list + accounts + .get_accounts() + .next() + .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? + .mnemonic() + .clone() + } + }; - Ok(mnemonic) + Ok(mnemonic) } #[tauri::command] pub async fn sign_in_with_password_and_account_id( - account_id: &str, - password: &str, - state: tauri::State<'_, Arc>>, + account_id: &str, + password: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - log::info!("Signing in with password"); + log::info!("Signing in with password"); - // Currently we only support a single, default, id in the wallet - let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); - let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; + // Currently we only support a single, default, id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id.to_string()); + let password = wallet_storage::UserPassword::new(password.to_string()); + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; - let mnemonic = extract_mnemonic(&stored_login, &account_id)?; - let first_login_id_when_converting = login_id.into(); - set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()).await?; + let mnemonic = extract_mnemonic(&stored_login, &account_id)?; + let first_login_id_when_converting = login_id.into(); + set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()) + .await?; - _connect_with_mnemonic(mnemonic, state).await + _connect_with_mnemonic(mnemonic, state).await } fn extract_mnemonic( - stored_login: &wallet_storage::StoredLogin, - account_id: &wallet_storage::AccountId, + stored_login: &wallet_storage::StoredLogin, + account_id: &wallet_storage::AccountId, ) -> Result { - let mnemonic = match stored_login { - wallet_storage::StoredLogin::Mnemonic(_) => { - return Err(BackendError::WalletNoSuchAccountIdInWalletLogin); - } - wallet_storage::StoredLogin::Multiple(ref accounts) => accounts - .get_account(account_id) - .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? - .mnemonic() - .clone(), - }; - Ok(mnemonic) + let mnemonic = match stored_login { + wallet_storage::StoredLogin::Mnemonic(_) => { + return Err(BackendError::WalletNoSuchAccountIdInWalletLogin); + } + wallet_storage::StoredLogin::Multiple(ref accounts) => accounts + .get_account(account_id) + .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? + .mnemonic() + .clone(), + }; + Ok(mnemonic) } #[tauri::command] pub fn remove_password() -> Result<(), BackendError> { - log::info!("Removing password"); - let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - wallet_storage::remove_login(&login_id) + log::info!("Removing password"); + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + wallet_storage::remove_login(&login_id) } #[tauri::command] pub async fn add_account_for_password( - mnemonic: &str, - password: &str, - account_id: &str, - state: tauri::State<'_, Arc>>, + mnemonic: &str, + password: &str, + account_id: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - log::info!("Adding account for the current password: {account_id}"); - let mnemonic = Mnemonic::from_str(mnemonic)?; - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - // Currently we only support a single, default, login id in the wallet - let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); + log::info!("Adding account for the current password: {account_id}"); + let mnemonic = Mnemonic::from_str(mnemonic)?; + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + // Currently we only support a single, default, login id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id.to_string()); + let password = wallet_storage::UserPassword::new(password.to_string()); - wallet_storage::append_account_to_login( - mnemonic.clone(), - hd_path, - login_id.clone(), - account_id.clone(), - &password, - )?; + wallet_storage::append_account_to_login( + mnemonic.clone(), + hd_path, + login_id.clone(), + account_id.clone(), + &password, + )?; - let address = { - let state = state.read().await; - let network: Network = state.current_network().into(); - derive_address(mnemonic, network.bech32_prefix())?.to_string() - }; + let address = { + let state = state.read().await; + let network: Network = state.current_network().into(); + derive_address(mnemonic, network.bech32_prefix())?.to_string() + }; - // Re-read all the acccounts from the wallet to reset the state, rather than updating it - // incrementally - let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; - // NOTE: since we are appending, this id shouldn't be needed, but setting the state is supposed - // to be a general function - let first_id_when_converting = login_id.into(); - set_state_with_all_accounts(stored_login, first_id_when_converting, state).await?; + // Re-read all the acccounts from the wallet to reset the state, rather than updating it + // incrementally + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; + // NOTE: since we are appending, this id shouldn't be needed, but setting the state is supposed + // to be a general function + let first_id_when_converting = login_id.into(); + set_state_with_all_accounts(stored_login, first_id_when_converting, state).await?; - Ok(AccountEntry { - id: account_id.to_string(), - address, - }) + Ok(AccountEntry { + id: account_id.to_string(), + address, + }) } // The first `AccoundId` when converting is the `LoginId` for the entry that was loaded. async fn set_state_with_all_accounts( - stored_login: wallet_storage::StoredLogin, - first_id_when_converting: wallet_storage::AccountId, - state: tauri::State<'_, Arc>>, + stored_login: wallet_storage::StoredLogin, + first_id_when_converting: wallet_storage::AccountId, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - log::trace!("Set state with accounts:"); - let all_accounts: Vec<_> = stored_login - .unwrap_into_multiple_accounts(first_id_when_converting) - .into_accounts() - .collect(); + log::trace!("Set state with accounts:"); + let all_accounts: Vec<_> = stored_login + .unwrap_into_multiple_accounts(first_id_when_converting) + .into_accounts() + .collect(); - for account in &all_accounts { - log::trace!("account: {:?}", account.id()); - } + for account in &all_accounts { + log::trace!("account: {:?}", account.id()); + } - let all_account_ids: Vec = all_accounts - .iter() - .map(|account| { - let mnemonic = account.mnemonic(); - let addresses: HashMap = WalletNetwork::iter() - .map(|network| { - let config_network: Network = network.into(); - ( - network, - derive_address(mnemonic.clone(), config_network.bech32_prefix()).unwrap(), - ) + let all_account_ids: Vec = all_accounts + .iter() + .map(|account| { + let mnemonic = account.mnemonic(); + let addresses: HashMap = WalletNetwork::iter() + .map(|network| { + let config_network: Network = network.into(); + ( + network, + derive_address(mnemonic.clone(), config_network.bech32_prefix()).unwrap(), + ) + }) + .collect(); + WalletAccountIds { + id: account.id().clone(), + addresses, + } }) .collect(); - WalletAccountIds { - id: account.id().clone(), - addresses, - } - }) - .collect(); - let mut w_state = state.write().await; - w_state.set_all_accounts(all_account_ids); - Ok(()) + let mut w_state = state.write().await; + w_state.set_all_accounts(all_account_ids); + Ok(()) } #[tauri::command] pub async fn remove_account_for_password( - password: &str, - account_id: &str, - state: tauri::State<'_, Arc>>, + password: &str, + account_id: &str, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - log::info!("Removing account: {account_id}"); - // Currently we only support a single, default, id in the wallet - let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); - wallet_storage::remove_account_from_login(&login_id, &account_id, &password)?; + log::info!("Removing account: {account_id}"); + // Currently we only support a single, default, id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id.to_string()); + let password = wallet_storage::UserPassword::new(password.to_string()); + wallet_storage::remove_account_from_login(&login_id, &account_id, &password)?; - // Load to reset the internal state - let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; - // NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting - // the state is supposed to be a general function - let first_account_id_when_converting = login_id.into(); - set_state_with_all_accounts(stored_login, first_account_id_when_converting, state).await + // Load to reset the internal state + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; + // NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting + // the state is supposed to be a general function + let first_account_id_when_converting = login_id.into(); + set_state_with_all_accounts(stored_login, first_account_id_when_converting, state).await } fn derive_address( - mnemonic: bip39::Mnemonic, - prefix: &str, + mnemonic: bip39::Mnemonic, + prefix: &str, ) -> Result { - DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)? - .try_derive_accounts()? - .first() - .map(AccountData::address) - .cloned() - .ok_or(BackendError::FailedToDeriveAddress) + DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)? + .try_derive_accounts()? + .first() + .map(AccountData::address) + .cloned() + .ok_or(BackendError::FailedToDeriveAddress) } #[tauri::command] pub async fn list_accounts( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - log::trace!("Listing accounts"); - let state = state.read().await; - let network = state.current_network(); + log::trace!("Listing accounts"); + let state = state.read().await; + let network = state.current_network(); - let all_accounts = state - .get_all_accounts() - .map(|account| AccountEntry { - id: account.id.to_string(), - address: account.addresses[&network].to_string(), - }) - .map(|account| { - log::trace!("{:?}", account); - account - }) - .collect(); + let all_accounts = state + .get_all_accounts() + .map(|account| AccountEntry { + id: account.id.to_string(), + address: account.addresses[&network].to_string(), + }) + .map(|account| { + log::trace!("{:?}", account); + account + }) + .collect(); - Ok(all_accounts) + Ok(all_accounts) } #[tauri::command] pub fn show_mnemonic_for_account_in_password( - account_id: String, - password: String, + account_id: String, + password: String, ) -> Result { - log::info!("Getting mnemonic for: {account_id}"); - let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let account_id = wallet_storage::AccountId::new(account_id); - let password = wallet_storage::UserPassword::new(password); - let mnemonic = _show_mnemonic_for_account_in_password(&login_id, &account_id, &password)?; - Ok(mnemonic.to_string()) + log::info!("Getting mnemonic for: {account_id}"); + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id); + let password = wallet_storage::UserPassword::new(password); + let mnemonic = _show_mnemonic_for_account_in_password(&login_id, &account_id, &password)?; + Ok(mnemonic.to_string()) } fn _show_mnemonic_for_account_in_password( - login_id: &wallet_storage::LoginId, - account_id: &wallet_storage::AccountId, - password: &wallet_storage::UserPassword, + login_id: &wallet_storage::LoginId, + account_id: &wallet_storage::AccountId, + password: &wallet_storage::UserPassword, ) -> Result { - let stored_account = wallet_storage::load_existing_login(login_id, password)?; - let mnemonic = match stored_account { - wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), - wallet_storage::StoredLogin::Multiple(ref accounts) => { - for account in accounts.get_accounts() { - log::debug!("{:?}", account); - } - accounts - .get_account(account_id) - .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? - .mnemonic() - .clone() - } - }; - Ok(mnemonic) + let stored_account = wallet_storage::load_existing_login(login_id, password)?; + let mnemonic = match stored_account { + wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), + wallet_storage::StoredLogin::Multiple(ref accounts) => { + for account in accounts.get_accounts() { + log::debug!("{:?}", account); + } + accounts + .get_account(account_id) + .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? + .mnemonic() + .clone() + } + }; + Ok(mnemonic) } #[cfg(test)] mod tests { - use super::*; + use super::*; - use std::path::PathBuf; + use std::path::PathBuf; - use crate::wallet_storage::{ - self, - account_data::{MnemonicAccount, WalletAccount}, - }; + use crate::wallet_storage::{ + self, + account_data::{MnemonicAccount, WalletAccount}, + }; - // This decryptes a stored wallet file using the same procedure as when signing in. Most tests - // related to the encryped wallet storage is in `wallet_storage`. - #[test] - fn decrypt_stored_wallet_for_sign_in() { - const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; - let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); - let login_id = wallet_storage::LoginId::new("first".to_string()); - let account_id = wallet_storage::AccountId::new("first".to_string()); - let password = wallet_storage::UserPassword::new("password".to_string()); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + // This decryptes a stored wallet file using the same procedure as when signing in. Most tests + // related to the encryped wallet storage is in `wallet_storage`. + #[test] + fn decrypt_stored_wallet_for_sign_in() { + const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; + let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); + let login_id = wallet_storage::LoginId::new("first".to_string()); + let account_id = wallet_storage::AccountId::new("first".to_string()); + let password = wallet_storage::UserPassword::new("password".to_string()); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let stored_login = - wallet_storage::load_existing_login_at_file(&wallet_file, &login_id, &password).unwrap(); - let mnemonic = extract_first_mnemonic(&stored_login).unwrap(); + let stored_login = + wallet_storage::load_existing_login_at_file(&wallet_file, &login_id, &password) + .unwrap(); + let mnemonic = extract_first_mnemonic(&stored_login).unwrap(); - let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); - assert_eq!(mnemonic, expected_mnemonic); + let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); + assert_eq!(mnemonic, expected_mnemonic); - let all_accounts: Vec<_> = stored_login - .unwrap_into_multiple_accounts(account_id.clone()) - .into_accounts() - .collect(); + let all_accounts: Vec<_> = stored_login + .unwrap_into_multiple_accounts(account_id.clone()) + .into_accounts() + .collect(); - assert_eq!( - all_accounts, - vec![WalletAccount::new( - account_id, - MnemonicAccount::new(expected_mnemonic, hd_path), - )] - ); - } + assert_eq!( + all_accounts, + vec![WalletAccount::new( + account_id, + MnemonicAccount::new(expected_mnemonic, hd_path), + )] + ); + } - #[test] - fn decrypt_stored_wallet_multiple_for_sign_in() { - // WIP(JON): same as above but with file containing multiple accounts - } + #[test] + fn decrypt_stored_wallet_multiple_for_sign_in() { + // WIP(JON): same as above but with file containing multiple accounts + } } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs index 34f792b08e..598e108d97 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs @@ -13,52 +13,52 @@ use validator_client::nymd::Fee; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/stateparams.ts"))] #[derive(Serialize, Deserialize)] pub struct TauriContractStateParams { - minimum_mixnode_pledge: String, - minimum_gateway_pledge: String, - mixnode_rewarded_set_size: u32, - mixnode_active_set_size: u32, + minimum_mixnode_pledge: String, + minimum_gateway_pledge: String, + mixnode_rewarded_set_size: u32, + mixnode_active_set_size: u32, } impl From for TauriContractStateParams { - fn from(p: ContractStateParams) -> TauriContractStateParams { - TauriContractStateParams { - minimum_mixnode_pledge: p.minimum_mixnode_pledge.to_string(), - minimum_gateway_pledge: p.minimum_gateway_pledge.to_string(), - mixnode_rewarded_set_size: p.mixnode_rewarded_set_size, - mixnode_active_set_size: p.mixnode_active_set_size, + fn from(p: ContractStateParams) -> TauriContractStateParams { + TauriContractStateParams { + minimum_mixnode_pledge: p.minimum_mixnode_pledge.to_string(), + minimum_gateway_pledge: p.minimum_gateway_pledge.to_string(), + mixnode_rewarded_set_size: p.mixnode_rewarded_set_size, + mixnode_active_set_size: p.mixnode_active_set_size, + } } - } } impl TryFrom for ContractStateParams { - type Error = BackendError; + type Error = BackendError; - fn try_from(p: TauriContractStateParams) -> Result { - Ok(ContractStateParams { - minimum_mixnode_pledge: Uint128::try_from(p.minimum_mixnode_pledge.as_str())?, - minimum_gateway_pledge: Uint128::try_from(p.minimum_gateway_pledge.as_str())?, - mixnode_rewarded_set_size: p.mixnode_rewarded_set_size, - mixnode_active_set_size: p.mixnode_active_set_size, - }) - } + fn try_from(p: TauriContractStateParams) -> Result { + Ok(ContractStateParams { + minimum_mixnode_pledge: Uint128::try_from(p.minimum_mixnode_pledge.as_str())?, + minimum_gateway_pledge: Uint128::try_from(p.minimum_gateway_pledge.as_str())?, + mixnode_rewarded_set_size: p.mixnode_rewarded_set_size, + mixnode_active_set_size: p.mixnode_active_set_size, + }) + } } #[tauri::command] pub async fn get_contract_settings( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok(nymd_client!(state).get_contract_settings().await?.into()) + Ok(nymd_client!(state).get_contract_settings().await?.into()) } #[tauri::command] pub async fn update_contract_settings( - params: TauriContractStateParams, - fee: Option, - state: tauri::State<'_, Arc>>, + params: TauriContractStateParams, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; - nymd_client!(state) - .update_contract_settings(mixnet_contract_settings_params.clone(), fee) - .await?; - Ok(mixnet_contract_settings_params.into()) + let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; + nymd_client!(state) + .update_contract_settings(mixnet_contract_settings_params.clone(), fee) + .await?; + Ok(mixnet_contract_settings_params.into()) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index e1a0c11e85..938896e769 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -11,90 +11,90 @@ use validator_client::nymd::Fee; #[tauri::command] pub async fn bond_gateway( - gateway: Gateway, - pledge: Coin, - owner_signature: String, - fee: Option, - state: tauri::State<'_, Arc>>, + gateway: Gateway, + pledge: Coin, + owner_signature: String, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; - nymd_client!(state) - .bond_gateway(gateway, owner_signature, pledge, fee) - .await?; - Ok(()) + let denom = state.read().await.current_network().denom(); + let pledge = pledge.into_cosmwasm_coin(&denom)?; + nymd_client!(state) + .bond_gateway(gateway, owner_signature, pledge, fee) + .await?; + Ok(()) } #[tauri::command] pub async fn unbond_gateway( - fee: Option, - state: tauri::State<'_, Arc>>, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - nymd_client!(state).unbond_gateway(fee).await?; - Ok(()) + nymd_client!(state).unbond_gateway(fee).await?; + Ok(()) } #[tauri::command] pub async fn unbond_mixnode( - fee: Option, - state: tauri::State<'_, Arc>>, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - nymd_client!(state).unbond_mixnode(fee).await?; - Ok(()) + nymd_client!(state).unbond_mixnode(fee).await?; + Ok(()) } #[tauri::command] pub async fn bond_mixnode( - mixnode: MixNode, - owner_signature: String, - pledge: Coin, - fee: Option, - state: tauri::State<'_, Arc>>, + mixnode: MixNode, + owner_signature: String, + pledge: Coin, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; - nymd_client!(state) - .bond_mixnode(mixnode, owner_signature, pledge, fee) - .await?; - Ok(()) + let denom = state.read().await.current_network().denom(); + let pledge = pledge.into_cosmwasm_coin(&denom)?; + nymd_client!(state) + .bond_mixnode(mixnode, owner_signature, pledge, fee) + .await?; + Ok(()) } #[tauri::command] pub async fn update_mixnode( - profit_margin_percent: u8, - fee: Option, - state: tauri::State<'_, Arc>>, + profit_margin_percent: u8, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - nymd_client!(state) - .update_mixnode_config(profit_margin_percent, fee) - .await?; - Ok(()) + nymd_client!(state) + .update_mixnode_config(profit_margin_percent, fee) + .await?; + Ok(()) } #[tauri::command] pub async fn mixnode_bond_details( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - let guard = state.read().await; - let client = guard.current_client()?; - let bond = client.nymd.owns_mixnode(client.nymd.address()).await?; - Ok(bond) + let guard = state.read().await; + let client = guard.current_client()?; + let bond = client.nymd.owns_mixnode(client.nymd.address()).await?; + Ok(bond) } #[tauri::command] pub async fn gateway_bond_details( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - let guard = state.read().await; - let client = guard.current_client()?; - let bond = client.nymd.owns_gateway(client.nymd.address()).await?; - Ok(bond) + let guard = state.read().await; + let client = guard.current_client()?; + let bond = client.nymd.owns_gateway(client.nymd.address()).await?; + Ok(bond) } #[tauri::command] pub async fn get_operator_rewards( - address: String, - state: tauri::State<'_, Arc>>, + address: String, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok(nymd_client!(state).get_operator_rewards(address).await?) + Ok(nymd_client!(state).get_operator_rewards(address).await?) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index a3becdfdfe..ebd4ebec54 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -12,74 +12,68 @@ use validator_client::nymd::Fee; #[tauri::command] pub async fn get_pending_delegation_events( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - Ok( - nymd_client!(state) - .get_pending_delegation_events(nymd_client!(state).address().to_string(), None) - .await? - .into_iter() - .map(|delegation_event| delegation_event.into()) - .collect::>(), - ) + Ok(nymd_client!(state) + .get_pending_delegation_events(nymd_client!(state).address().to_string(), None) + .await? + .into_iter() + .map(|delegation_event| delegation_event.into()) + .collect::>()) } #[tauri::command] pub async fn delegate_to_mixnode( - identity: &str, - amount: Coin, - fee: Option, - state: tauri::State<'_, Arc>>, + identity: &str, + amount: Coin, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - let denom = state.read().await.current_network().denom(); - let delegation: CosmWasmCoin = amount.into_cosmwasm_coin(&denom)?; - nymd_client!(state) - .delegate_to_mixnode(identity, &delegation, fee) - .await?; - Ok(DelegationResult::new( - nymd_client!(state).address().as_ref(), - identity, - Some(delegation.into()), - )) + let denom = state.read().await.current_network().denom(); + let delegation: CosmWasmCoin = amount.into_cosmwasm_coin(&denom)?; + nymd_client!(state) + .delegate_to_mixnode(identity, &delegation, fee) + .await?; + Ok(DelegationResult::new( + nymd_client!(state).address().as_ref(), + identity, + Some(delegation.into()), + )) } #[tauri::command] pub async fn undelegate_from_mixnode( - identity: &str, - fee: Option, - state: tauri::State<'_, Arc>>, + identity: &str, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - nymd_client!(state) - .remove_mixnode_delegation(identity, fee) - .await?; - Ok(DelegationResult::new( - nymd_client!(state).address().as_ref(), - identity, - None, - )) + nymd_client!(state) + .remove_mixnode_delegation(identity, fee) + .await?; + Ok(DelegationResult::new( + nymd_client!(state).address().as_ref(), + identity, + None, + )) } #[tauri::command] pub async fn get_reverse_mix_delegations_paged( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .get_delegator_delegations_paged(nymd_client!(state).address().to_string(), None, None) - .await?, - ) + Ok(nymd_client!(state) + .get_delegator_delegations_paged(nymd_client!(state).address().to_string(), None, None) + .await?) } #[tauri::command] pub async fn get_delegator_rewards( - address: String, - mix_identity: IdentityKey, - proxy: Option, - state: tauri::State<'_, Arc>>, + address: String, + mix_identity: IdentityKey, + proxy: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .get_delegator_rewards(address, mix_identity, proxy) - .await?, - ) + Ok(nymd_client!(state) + .get_delegator_rewards(address, mix_identity, proxy) + .await?) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs b/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs index 085564ca06..263e0fcf6d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs @@ -10,27 +10,27 @@ use tokio::sync::RwLock; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/epoch.ts"))] #[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)] pub struct Epoch { - id: u32, - start: i64, - end: i64, - duration_seconds: u64, + id: u32, + start: i64, + end: i64, + duration_seconds: u64, } impl From for Epoch { - fn from(interval: Interval) -> Self { - Self { - id: interval.id(), - start: interval.start_unix_timestamp(), - end: interval.end_unix_timestamp(), - duration_seconds: interval.length().as_secs(), + fn from(interval: Interval) -> Self { + Self { + id: interval.id(), + start: interval.start_unix_timestamp(), + end: interval.end_unix_timestamp(), + duration_seconds: interval.length().as_secs(), + } } - } } #[tauri::command] pub async fn get_current_epoch( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - let interval = nymd_client!(state).get_current_epoch().await?; - Ok(interval.into()) + let interval = nymd_client!(state).get_current_epoch().await?; + Ok(interval.into()) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/send.rs b/nym-wallet/src-tauri/src/operations/mixnet/send.rs index 5725f0ca55..a50730ab5a 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/send.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/send.rs @@ -12,59 +12,59 @@ use validator_client::nymd::{AccountId, CosmosCoin, Fee, TxResponse}; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/tauritxresult.ts"))] #[derive(Deserialize, Serialize)] pub struct TauriTxResult { - block_height: u64, - code: u32, - details: TransactionDetails, - gas_used: u64, - gas_wanted: u64, - tx_hash: String, + block_height: u64, + code: u32, + details: TransactionDetails, + gas_used: u64, + gas_wanted: u64, + tx_hash: String, } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr( - test, - ts(export, export_to = "../src/types/rust/transactiondetails.ts") + test, + ts(export, export_to = "../src/types/rust/transactiondetails.ts") )] #[derive(Deserialize, Serialize)] pub struct TransactionDetails { - amount: Coin, - from_address: String, - to_address: String, + amount: Coin, + from_address: String, + to_address: String, } impl TauriTxResult { - fn new(t: TxResponse, details: TransactionDetails) -> TauriTxResult { - TauriTxResult { - block_height: t.height.value(), - code: t.tx_result.code.value(), - details, - gas_used: t.tx_result.gas_used.value(), - gas_wanted: t.tx_result.gas_wanted.value(), - tx_hash: t.hash.to_string(), + fn new(t: TxResponse, details: TransactionDetails) -> TauriTxResult { + TauriTxResult { + block_height: t.height.value(), + code: t.tx_result.code.value(), + details, + gas_used: t.tx_result.gas_used.value(), + gas_wanted: t.tx_result.gas_wanted.value(), + tx_hash: t.hash.to_string(), + } } - } } #[tauri::command] pub async fn send( - address: &str, - amount: Coin, - memo: String, - fee: Option, - state: tauri::State<'_, Arc>>, + address: &str, + amount: Coin, + memo: String, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - let address = AccountId::from_str(address)?; - let network_denom = state.read().await.current_network().denom(); - let cosmos_amount: CosmosCoin = amount.clone().into_cosmos_coin(&network_denom)?; - let result = nymd_client!(state) - .send(&address, vec![cosmos_amount], memo, fee) - .await?; - Ok(TauriTxResult::new( - result, - TransactionDetails { - from_address: nymd_client!(state).address().to_string(), - to_address: address.to_string(), - amount, - }, - )) + let address = AccountId::from_str(address)?; + let network_denom = state.read().await.current_network().denom(); + let cosmos_amount: CosmosCoin = amount.clone().into_cosmos_coin(&network_denom)?; + let result = nymd_client!(state) + .send(&address, vec![cosmos_amount], memo, fee) + .await?; + Ok(TauriTxResult::new( + result, + TransactionDetails { + from_address: nymd_client!(state).address().to_string(), + to_address: address.to_string(), + amount, + }, + )) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index 52f3b4b074..cbfa8e39f9 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -11,22 +11,22 @@ use tokio::sync::RwLock; #[tauri::command] pub async fn simulate_update_contract_settings( - params: TauriContractStateParams, - state: tauri::State<'_, Arc>>, + params: TauriContractStateParams, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; - let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; + let guard = state.read().await; + let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::UpdateContractStateParams(mixnet_contract_settings_params), - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::UpdateContractStateParams(mixnet_contract_settings_params), + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs index 2aaa8bda1d..e2647c69ea 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs @@ -12,27 +12,27 @@ use validator_client::nymd::{AccountId, MsgSend}; #[tauri::command] pub async fn simulate_send( - address: &str, - amount: Coin, - state: tauri::State<'_, Arc>>, + address: &str, + amount: Coin, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let to_address = AccountId::from_str(address)?; - let network_denom = guard.current_network().denom(); - let amount = vec![amount.clone().into_cosmos_coin(&network_denom)?]; + let to_address = AccountId::from_str(address)?; + let network_denom = guard.current_network().denom(); + let amount = vec![amount.clone().into_cosmos_coin(&network_denom)?]; - let client = guard.current_client()?; - let from_address = client.nymd.address().clone(); - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let from_address = client.nymd.address().clone(); + let gas_price = client.nymd.gas_price().clone(); - // TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code - let msg = MsgSend { - from_address, - to_address, - amount, - }; + // TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code + let msg = MsgSend { + from_address, + to_address, + amount, + }; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 0fe99ad11c..014e003cbc 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -11,169 +11,169 @@ use tokio::sync::RwLock; #[tauri::command] pub async fn simulate_bond_gateway( - gateway: Gateway, - pledge: Coin, - owner_signature: String, - state: tauri::State<'_, Arc>>, + gateway: Gateway, + pledge: Coin, + owner_signature: String, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmos_coin(&network_denom)?; + let guard = state.read().await; + let network_denom = guard.current_network().denom(); + let pledge = pledge.into_cosmos_coin(&network_denom)?; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - // TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::BondGateway { - gateway, - owner_signature, - }, - vec![pledge], - )?; + // TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::BondGateway { + gateway, + owner_signature, + }, + vec![pledge], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_unbond_gateway( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::UnbondGateway {}, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::UnbondGateway {}, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_bond_mixnode( - mixnode: MixNode, - owner_signature: String, - pledge: Coin, - state: tauri::State<'_, Arc>>, + mixnode: MixNode, + owner_signature: String, + pledge: Coin, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmos_coin(&network_denom)?; + let guard = state.read().await; + let network_denom = guard.current_network().denom(); + let pledge = pledge.into_cosmos_coin(&network_denom)?; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::BondMixnode { - mix_node: mixnode, - owner_signature, - }, - vec![pledge], - )?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::BondMixnode { + mix_node: mixnode, + owner_signature, + }, + vec![pledge], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_unbond_mixnode( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::UnbondMixnode {}, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::UnbondMixnode {}, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_update_mixnode( - profit_margin_percent: u8, - state: tauri::State<'_, Arc>>, + profit_margin_percent: u8, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::UpdateMixnodeConfig { - profit_margin_percent, - }, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_delegate_to_mixnode( - identity: &str, - amount: Coin, - state: tauri::State<'_, Arc>>, + identity: &str, + amount: Coin, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let delegation = amount.into_cosmos_coin(&network_denom)?; + let guard = state.read().await; + let network_denom = guard.current_network().denom(); + let delegation = amount.into_cosmos_coin(&network_denom)?; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::DelegateToMixnode { - mix_identity: identity.to_string(), - }, - vec![delegation], - )?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::DelegateToMixnode { + mix_identity: identity.to_string(), + }, + vec![delegation], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_undelegate_from_mixnode( - identity: &str, - state: tauri::State<'_, Arc>>, + identity: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let mixnet_contract = client.nymd.mixnet_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::UndelegateFromMixnode { - mix_identity: identity.to_string(), - }, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::UndelegateFromMixnode { + mix_identity: identity.to_string(), + }, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/mod.rs b/nym-wallet/src-tauri/src/operations/simulate/mod.rs index d0f333ca4e..273f3d05ab 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mod.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mod.rs @@ -14,52 +14,50 @@ pub mod vesting; // technically we could have also exposed a result: Option field from the SimulateResponse, // but in the context of the wallet it's really irrelevant and useless for the time being pub(crate) struct SimulateResult { - // As I mentioned somewhere before, from what I've seen in manual testing, - // gas estimation does not exist if transaction itself fails to get executed. - // for example if you attempt to send a 'BondMixnode' with invalid signature - pub gas_info: Option, - pub gas_price: GasPrice, + // As I mentioned somewhere before, from what I've seen in manual testing, + // gas estimation does not exist if transaction itself fails to get executed. + // for example if you attempt to send a 'BondMixnode' with invalid signature + pub gas_info: Option, + pub gas_price: GasPrice, } impl SimulateResult { - pub fn new(gas_info: Option, gas_price: GasPrice) -> Self { - SimulateResult { - gas_info, - gas_price, + pub fn new(gas_info: Option, gas_price: GasPrice) -> Self { + SimulateResult { + gas_info, + gas_price, + } } - } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FeeDetails { - // expected to be used by the wallet in order to display detailed fee information to the user - pub amount: Option, - pub fee: Fee, + // expected to be used by the wallet in order to display detailed fee information to the user + pub amount: Option, + pub fee: Fee, } impl SimulateResult { - pub fn detailed_fee(&self) -> FeeDetails { - FeeDetails { - amount: self.to_fee_amount().map(Into::into), - fee: self.to_fee(), + pub fn detailed_fee(&self) -> FeeDetails { + FeeDetails { + amount: self.to_fee_amount().map(Into::into), + fee: self.to_fee(), + } } - } - fn to_fee_amount(&self) -> Option { - self - .gas_info - .map(|gas_info| &self.gas_price * gas_info.gas_used) - } + fn to_fee_amount(&self) -> Option { + self.gas_info + .map(|gas_info| &self.gas_price * gas_info.gas_used) + } - fn to_fee(&self) -> Fee { - self - .to_fee_amount() - .and_then(|fee_amount| { - self.gas_info.map(|gas_info| { - let gas_limit = gas_info.gas_used; - tx::Fee::from_amount_and_gas(fee_amount, gas_limit).into() - }) - }) - .unwrap_or_default() - } + fn to_fee(&self) -> Fee { + self.to_fee_amount() + .and_then(|fee_amount| { + self.gas_info.map(|gas_info| { + let gas_limit = gas_info.gas_used; + tx::Fee::from_amount_and_gas(fee_amount, gas_limit).into() + }) + }) + .unwrap_or_default() + } } diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index df969cf300..d424777496 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -12,144 +12,144 @@ use vesting_contract_common::ExecuteMsg; #[tauri::command] pub async fn simulate_vesting_bond_gateway( - gateway: Gateway, - pledge: Coin, - owner_signature: String, - state: tauri::State<'_, Arc>>, + gateway: Gateway, + pledge: Coin, + owner_signature: String, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&network_denom)?; + let guard = state.read().await; + let network_denom = guard.current_network().denom(); + let pledge = pledge.into_cosmwasm_coin(&network_denom)?; - let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let vesting_contract = client.nymd.vesting_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::BondGateway { - gateway, - owner_signature, - amount: pledge, - }, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::BondGateway { + gateway, + owner_signature, + amount: pledge, + }, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_vesting_unbond_gateway( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let vesting_contract = client.nymd.vesting_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::UnbondGateway {}, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::UnbondGateway {}, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_vesting_bond_mixnode( - mixnode: MixNode, - owner_signature: String, - pledge: Coin, - state: tauri::State<'_, Arc>>, + mixnode: MixNode, + owner_signature: String, + pledge: Coin, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&network_denom)?; + let guard = state.read().await; + let network_denom = guard.current_network().denom(); + let pledge = pledge.into_cosmwasm_coin(&network_denom)?; - let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let vesting_contract = client.nymd.vesting_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::BondMixnode { - mix_node: mixnode, - owner_signature, - amount: pledge, - }, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::BondMixnode { + mix_node: mixnode, + owner_signature, + amount: pledge, + }, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_vesting_unbond_mixnode( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let vesting_contract = client.nymd.vesting_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::UnbondMixnode {}, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::UnbondMixnode {}, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_vesting_update_mixnode( - profit_margin_percent: u8, - state: tauri::State<'_, Arc>>, + profit_margin_percent: u8, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; + let guard = state.read().await; - let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let vesting_contract = client.nymd.vesting_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::UpdateMixnodeConfig { - profit_margin_percent, - }, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } #[tauri::command] pub async fn simulate_withdraw_vested_coins( - amount: Coin, - state: tauri::State<'_, Arc>>, + amount: Coin, + state: tauri::State<'_, Arc>>, ) -> Result { - let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let amount = amount.into_cosmwasm_coin(&network_denom)?; + let guard = state.read().await; + let network_denom = guard.current_network().denom(); + let amount = amount.into_cosmwasm_coin(&network_denom)?; - let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; - let gas_price = client.nymd.gas_price().clone(); + let client = guard.current_client()?; + let vesting_contract = client.nymd.vesting_contract_address()?; + let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::WithdrawVestedCoins { amount }, - vec![], - )?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::WithdrawVestedCoins { amount }, + vec![], + )?; - let result = client.nymd.simulate(vec![msg]).await?; - Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) + let result = client.nymd.simulate(vec![msg]).await?; + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } diff --git a/nym-wallet/src-tauri/src/operations/validator_api/status.rs b/nym-wallet/src-tauri/src/operations/validator_api/status.rs index 6b559b4acd..721c39bf82 100644 --- a/nym-wallet/src-tauri/src/operations/validator_api/status.rs +++ b/nym-wallet/src-tauri/src/operations/validator_api/status.rs @@ -7,76 +7,66 @@ use crate::state::State; use std::sync::Arc; use tokio::sync::RwLock; use validator_client::models::{ - CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse, - RewardEstimationResponse, StakeSaturationResponse, + CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse, + RewardEstimationResponse, StakeSaturationResponse, }; #[tauri::command] pub async fn mixnode_core_node_status( - identity: &str, - since: Option, - state: tauri::State<'_, Arc>>, + identity: &str, + since: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - api_client!(state) - .get_mixnode_core_status_count(identity, since) - .await?, - ) + Ok(api_client!(state) + .get_mixnode_core_status_count(identity, since) + .await?) } #[tauri::command] pub async fn gateway_core_node_status( - identity: &str, - since: Option, - state: tauri::State<'_, Arc>>, + identity: &str, + since: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - api_client!(state) - .get_gateway_core_status_count(identity, since) - .await?, - ) + Ok(api_client!(state) + .get_gateway_core_status_count(identity, since) + .await?) } #[tauri::command] pub async fn mixnode_status( - identity: &str, - state: tauri::State<'_, Arc>>, + identity: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok(api_client!(state).get_mixnode_status(identity).await?) + Ok(api_client!(state).get_mixnode_status(identity).await?) } #[tauri::command] pub async fn mixnode_reward_estimation( - identity: &str, - state: tauri::State<'_, Arc>>, + identity: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - api_client!(state) - .get_mixnode_reward_estimation(identity) - .await?, - ) + Ok(api_client!(state) + .get_mixnode_reward_estimation(identity) + .await?) } #[tauri::command] pub async fn mixnode_stake_saturation( - identity: &str, - state: tauri::State<'_, Arc>>, + identity: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - api_client!(state) - .get_mixnode_stake_saturation(identity) - .await?, - ) + Ok(api_client!(state) + .get_mixnode_stake_saturation(identity) + .await?) } #[tauri::command] pub async fn mixnode_inclusion_probability( - identity: &str, - state: tauri::State<'_, Arc>>, + identity: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - api_client!(state) - .get_mixnode_inclusion_probability(identity) - .await?, - ) + Ok(api_client!(state) + .get_mixnode_inclusion_probability(identity) + .await?) } diff --git a/nym-wallet/src-tauri/src/operations/vesting/bond.rs b/nym-wallet/src-tauri/src/operations/vesting/bond.rs index 0cfdb654d9..8a5b103cea 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/bond.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/bond.rs @@ -9,76 +9,76 @@ use validator_client::nymd::{Fee, VestingSigningClient}; #[tauri::command] pub async fn vesting_bond_gateway( - gateway: Gateway, - pledge: Coin, - owner_signature: String, - fee: Option, - state: tauri::State<'_, Arc>>, + gateway: Gateway, + pledge: Coin, + owner_signature: String, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; - nymd_client!(state) - .vesting_bond_gateway(gateway, &owner_signature, pledge, fee) - .await?; - Ok(()) + let denom = state.read().await.current_network().denom(); + let pledge = pledge.into_cosmwasm_coin(&denom)?; + nymd_client!(state) + .vesting_bond_gateway(gateway, &owner_signature, pledge, fee) + .await?; + Ok(()) } #[tauri::command] pub async fn vesting_unbond_gateway( - fee: Option, - state: tauri::State<'_, Arc>>, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - nymd_client!(state).vesting_unbond_gateway(fee).await?; - Ok(()) + nymd_client!(state).vesting_unbond_gateway(fee).await?; + Ok(()) } #[tauri::command] pub async fn vesting_unbond_mixnode( - fee: Option, - state: tauri::State<'_, Arc>>, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - nymd_client!(state).vesting_unbond_mixnode(fee).await?; - Ok(()) + nymd_client!(state).vesting_unbond_mixnode(fee).await?; + Ok(()) } #[tauri::command] pub async fn vesting_bond_mixnode( - mixnode: MixNode, - owner_signature: String, - pledge: Coin, - fee: Option, - state: tauri::State<'_, Arc>>, + mixnode: MixNode, + owner_signature: String, + pledge: Coin, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; - nymd_client!(state) - .vesting_bond_mixnode(mixnode, &owner_signature, pledge, fee) - .await?; - Ok(()) + let denom = state.read().await.current_network().denom(); + let pledge = pledge.into_cosmwasm_coin(&denom)?; + nymd_client!(state) + .vesting_bond_mixnode(mixnode, &owner_signature, pledge, fee) + .await?; + Ok(()) } #[tauri::command] pub async fn withdraw_vested_coins( - amount: Coin, - fee: Option, - state: tauri::State<'_, Arc>>, + amount: Coin, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let amount = amount.into_cosmwasm_coin(&denom)?; - nymd_client!(state) - .withdraw_vested_coins(amount, fee) - .await?; - Ok(()) + let denom = state.read().await.current_network().denom(); + let amount = amount.into_cosmwasm_coin(&denom)?; + nymd_client!(state) + .withdraw_vested_coins(amount, fee) + .await?; + Ok(()) } #[tauri::command] pub async fn vesting_update_mixnode( - profit_margin_percent: u8, - fee: Option, - state: tauri::State<'_, Arc>>, + profit_margin_percent: u8, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - nymd_client!(state) - .vesting_update_mixnode_config(profit_margin_percent, fee) - .await?; - Ok(()) + nymd_client!(state) + .vesting_update_mixnode_config(profit_margin_percent, fee) + .await?; + Ok(()) } diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs index 854b418609..94b6c654e8 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -10,56 +10,54 @@ use validator_client::nymd::{Fee, VestingSigningClient}; #[tauri::command] pub async fn get_pending_vesting_delegation_events( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - let guard = state.read().await; - let client = &guard.current_client()?.nymd; - let vesting_contract = client.vesting_contract_address()?; + let guard = state.read().await; + let client = &guard.current_client()?.nymd; + let vesting_contract = client.vesting_contract_address()?; - Ok( - client - .get_pending_delegation_events( - client.address().to_string(), - Some(vesting_contract.to_string()), - ) - .await? - .into_iter() - .map(|delegation_event| delegation_event.into()) - .collect::>(), - ) + Ok(client + .get_pending_delegation_events( + client.address().to_string(), + Some(vesting_contract.to_string()), + ) + .await? + .into_iter() + .map(|delegation_event| delegation_event.into()) + .collect::>()) } #[tauri::command] pub async fn vesting_delegate_to_mixnode( - identity: &str, - amount: Coin, - fee: Option, - state: tauri::State<'_, Arc>>, + identity: &str, + amount: Coin, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - let denom = state.read().await.current_network().denom(); - let delegation = amount.into_cosmwasm_coin(&denom)?; - nymd_client!(state) - .vesting_delegate_to_mixnode(identity, &delegation, fee) - .await?; - Ok(DelegationResult::new( - nymd_client!(state).address().as_ref(), - identity, - Some(delegation.into()), - )) + let denom = state.read().await.current_network().denom(); + let delegation = amount.into_cosmwasm_coin(&denom)?; + nymd_client!(state) + .vesting_delegate_to_mixnode(identity, &delegation, fee) + .await?; + Ok(DelegationResult::new( + nymd_client!(state).address().as_ref(), + identity, + Some(delegation.into()), + )) } #[tauri::command] pub async fn vesting_undelegate_from_mixnode( - identity: &str, - fee: Option, - state: tauri::State<'_, Arc>>, + identity: &str, + fee: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - nymd_client!(state) - .vesting_undelegate_from_mixnode(identity, fee) - .await?; - Ok(DelegationResult::new( - nymd_client!(state).address().as_ref(), - identity, - None, - )) + nymd_client!(state) + .vesting_undelegate_from_mixnode(identity, fee) + .await?; + Ok(DelegationResult::new( + nymd_client!(state).address().as_ref(), + identity, + None, + )) } diff --git a/nym-wallet/src-tauri/src/operations/vesting/mod.rs b/nym-wallet/src-tauri/src/operations/vesting/mod.rs index 9c4829e200..3542fd8d5c 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/mod.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/mod.rs @@ -13,90 +13,90 @@ pub mod queries; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/pledgedata.ts"))] #[derive(Serialize, Deserialize, Debug)] pub struct PledgeData { - pub amount: Coin, - pub block_time: u64, + pub amount: Coin, + pub block_time: u64, } impl From for PledgeData { - fn from(data: VestingPledgeData) -> Self { - Self { - amount: data.amount().into(), - block_time: data.block_time().seconds(), + fn from(data: VestingPledgeData) -> Self { + Self { + amount: data.amount().into(), + block_time: data.block_time().seconds(), + } } - } } impl PledgeData { - fn and_then(data: VestingPledgeData) -> Option { - Some(data.into()) - } + fn and_then(data: VestingPledgeData) -> Option { + Some(data.into()) + } } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr( - test, - ts(export, export_to = "../src/types/rust/originalvestingresponse.ts") + test, + ts(export, export_to = "../src/types/rust/originalvestingresponse.ts") )] #[derive(Serialize, Deserialize, Debug)] pub struct OriginalVestingResponse { - amount: Coin, - number_of_periods: usize, - period_duration: u64, + amount: Coin, + number_of_periods: usize, + period_duration: u64, } impl From for OriginalVestingResponse { - fn from(data: VestingOriginalVestingResponse) -> Self { - Self { - amount: data.amount().into(), - number_of_periods: data.number_of_periods(), - period_duration: data.period_duration(), + fn from(data: VestingOriginalVestingResponse) -> Self { + Self { + amount: data.amount().into(), + number_of_periods: data.number_of_periods(), + period_duration: data.period_duration(), + } } - } } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr( - test, - ts(export, export_to = "../src/types/rust/vestingaccountinfo.ts") + test, + ts(export, export_to = "../src/types/rust/vestingaccountinfo.ts") )] #[derive(Serialize, Deserialize, Debug)] pub struct VestingAccountInfo { - owner_address: String, - staking_address: Option, - start_time: u64, - periods: Vec, - coin: Coin, + owner_address: String, + staking_address: Option, + start_time: u64, + periods: Vec, + coin: Coin, } impl From for VestingAccountInfo { - fn from(account: VestingAccount) -> Self { - let mut periods = Vec::new(); - for period in account.periods() { - periods.push(period.into()); + fn from(account: VestingAccount) -> Self { + let mut periods = Vec::new(); + for period in account.periods() { + periods.push(period.into()); + } + Self { + owner_address: account.owner_address().to_string(), + staking_address: account.staking_address().map(|a| a.to_string()), + start_time: account.start_time().seconds(), + periods, + coin: account.coin().into(), + } } - Self { - owner_address: account.owner_address().to_string(), - staking_address: account.staking_address().map(|a| a.to_string()), - start_time: account.start_time().seconds(), - periods, - coin: account.coin().into(), - } - } } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/vestingperiod.ts"))] #[derive(Serialize, Deserialize, Debug)] pub struct VestingPeriod { - start_time: u64, - period_seconds: u64, + start_time: u64, + period_seconds: u64, } impl From for VestingPeriod { - fn from(period: VestingVestingPeriod) -> Self { - Self { - start_time: period.start_time, - period_seconds: period.period_seconds, + fn from(period: VestingVestingPeriod) -> Self { + Self { + start_time: period.start_time, + period_seconds: period.period_seconds, + } } - } } diff --git a/nym-wallet/src-tauri/src/operations/vesting/queries.rs b/nym-wallet/src-tauri/src/operations/vesting/queries.rs index e88f80a7c2..1e1b38f5cc 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/queries.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/queries.rs @@ -13,185 +13,161 @@ use super::{OriginalVestingResponse, PledgeData}; #[tauri::command] pub async fn locked_coins( - block_time: Option, - state: tauri::State<'_, Arc>>, + block_time: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .locked_coins( - nymd_client!(state).address().as_ref(), - block_time.map(Timestamp::from_seconds), - ) - .await? - .into(), - ) + Ok(nymd_client!(state) + .locked_coins( + nymd_client!(state).address().as_ref(), + block_time.map(Timestamp::from_seconds), + ) + .await? + .into()) } #[tauri::command] pub async fn spendable_coins( - block_time: Option, - state: tauri::State<'_, Arc>>, + block_time: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .spendable_coins( - nymd_client!(state).address().as_ref(), - block_time.map(Timestamp::from_seconds), - ) - .await? - .into(), - ) + Ok(nymd_client!(state) + .spendable_coins( + nymd_client!(state).address().as_ref(), + block_time.map(Timestamp::from_seconds), + ) + .await? + .into()) } #[tauri::command] pub async fn vested_coins( - vesting_account_address: &str, - block_time: Option, - state: tauri::State<'_, Arc>>, + vesting_account_address: &str, + block_time: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .vested_coins( - vesting_account_address, - block_time.map(Timestamp::from_seconds), - ) - .await? - .into(), - ) + Ok(nymd_client!(state) + .vested_coins( + vesting_account_address, + block_time.map(Timestamp::from_seconds), + ) + .await? + .into()) } #[tauri::command] pub async fn vesting_coins( - vesting_account_address: &str, - block_time: Option, - state: tauri::State<'_, Arc>>, + vesting_account_address: &str, + block_time: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .vesting_coins( - vesting_account_address, - block_time.map(Timestamp::from_seconds), - ) - .await? - .into(), - ) + Ok(nymd_client!(state) + .vesting_coins( + vesting_account_address, + block_time.map(Timestamp::from_seconds), + ) + .await? + .into()) } #[tauri::command] pub async fn vesting_start_time( - vesting_account_address: &str, - state: tauri::State<'_, Arc>>, + vesting_account_address: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .vesting_start_time(vesting_account_address) - .await? - .seconds(), - ) + Ok(nymd_client!(state) + .vesting_start_time(vesting_account_address) + .await? + .seconds()) } #[tauri::command] pub async fn vesting_end_time( - vesting_account_address: &str, - state: tauri::State<'_, Arc>>, + vesting_account_address: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .vesting_end_time(vesting_account_address) - .await? - .seconds(), - ) + Ok(nymd_client!(state) + .vesting_end_time(vesting_account_address) + .await? + .seconds()) } #[tauri::command] pub async fn original_vesting( - vesting_account_address: &str, - state: tauri::State<'_, Arc>>, + vesting_account_address: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .original_vesting(vesting_account_address) - .await? - .into(), - ) + Ok(nymd_client!(state) + .original_vesting(vesting_account_address) + .await? + .into()) } #[tauri::command] pub async fn delegated_free( - vesting_account_address: &str, - block_time: Option, - state: tauri::State<'_, Arc>>, + vesting_account_address: &str, + block_time: Option, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .delegated_free( - vesting_account_address, - block_time.map(Timestamp::from_seconds), - ) - .await? - .into(), - ) + Ok(nymd_client!(state) + .delegated_free( + vesting_account_address, + block_time.map(Timestamp::from_seconds), + ) + .await? + .into()) } #[tauri::command] pub async fn delegated_vesting( - block_time: Option, - vesting_account_address: &str, - state: tauri::State<'_, Arc>>, + block_time: Option, + vesting_account_address: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .delegated_vesting( - vesting_account_address, - block_time.map(Timestamp::from_seconds), - ) - .await? - .into(), - ) + Ok(nymd_client!(state) + .delegated_vesting( + vesting_account_address, + block_time.map(Timestamp::from_seconds), + ) + .await? + .into()) } #[tauri::command] pub async fn vesting_get_mixnode_pledge( - address: &str, - state: tauri::State<'_, Arc>>, + address: &str, + state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - Ok( - nymd_client!(state) - .get_mixnode_pledge(address) - .await? - .and_then(PledgeData::and_then), - ) + Ok(nymd_client!(state) + .get_mixnode_pledge(address) + .await? + .and_then(PledgeData::and_then)) } #[tauri::command] pub async fn vesting_get_gateway_pledge( - address: &str, - state: tauri::State<'_, Arc>>, + address: &str, + state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - Ok( - nymd_client!(state) - .get_gateway_pledge(address) - .await? - .and_then(PledgeData::and_then), - ) + Ok(nymd_client!(state) + .get_gateway_pledge(address) + .await? + .and_then(PledgeData::and_then)) } #[tauri::command] pub async fn get_current_vesting_period( - address: &str, - state: tauri::State<'_, Arc>>, + address: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .get_current_vesting_period(address) - .await?, - ) + Ok(nymd_client!(state) + .get_current_vesting_period(address) + .await?) } #[tauri::command] pub async fn get_account_info( - address: &str, - state: tauri::State<'_, Arc>>, + address: &str, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok(nymd_client!(state).get_account(address).await?.into()) + Ok(nymd_client!(state).get_account(address).await?.into()) } diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index cf889718e8..2fee2b09f0 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -17,450 +17,440 @@ use std::time::Duration; // Some hardcoded metadata overrides static METADATA_OVERRIDES: Lazy> = Lazy::new(|| { - vec![( - "https://rpc.nyx.nodes.guru/".parse().unwrap(), - ValidatorMetadata { - name: Some("Nodes.Guru".to_string()), - }, - )] + vec![( + "https://rpc.nyx.nodes.guru/".parse().unwrap(), + ValidatorMetadata { + name: Some("Nodes.Guru".to_string()), + }, + )] }); #[tauri::command] pub async fn load_config_from_files( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - state.write().await.load_config_files(); - Ok(()) + state.write().await.load_config_files(); + Ok(()) } #[tauri::command] pub async fn save_config_to_files( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - state.read().await.save_config_files() + state.read().await.save_config_files() } #[derive(Default)] pub struct State { - config: config::Config, - signing_clients: HashMap>, - current_network: Network, + config: config::Config, + signing_clients: HashMap>, + current_network: Network, - // All the accounts the we get from decrypting the wallet. We hold on to these for being able to - // switch accounts on-the-fly - all_accounts: Vec, + // All the accounts the we get from decrypting the wallet. We hold on to these for being able to + // switch accounts on-the-fly + all_accounts: Vec, - /// Validators that have been fetched dynamically, probably during startup. - fetched_validators: config::OptionalValidators, + /// Validators that have been fetched dynamically, probably during startup. + fetched_validators: config::OptionalValidators, - /// We fetch (and cache) some metadata, such as names, when available - validator_metadata: HashMap, + /// We fetch (and cache) some metadata, such as names, when available + validator_metadata: HashMap, } pub(crate) struct WalletAccountIds { - // The wallet account id - pub id: crate::wallet_storage::AccountId, - // The set of corresponding network identities derived from the mnemonic - pub addresses: HashMap, + // The wallet account id + pub id: crate::wallet_storage::AccountId, + // The set of corresponding network identities derived from the mnemonic + pub addresses: HashMap, } impl State { - pub fn client(&self, network: Network) -> Result<&Client, BackendError> { - self - .signing_clients - .get(&network) - .ok_or(BackendError::ClientNotInitialized) - } + pub fn client(&self, network: Network) -> Result<&Client, BackendError> { + self.signing_clients + .get(&network) + .ok_or(BackendError::ClientNotInitialized) + } - pub fn client_mut( - &mut self, - network: Network, - ) -> Result<&mut Client, BackendError> { - self - .signing_clients - .get_mut(&network) - .ok_or(BackendError::ClientNotInitialized) - } + pub fn client_mut( + &mut self, + network: Network, + ) -> Result<&mut Client, BackendError> { + self.signing_clients + .get_mut(&network) + .ok_or(BackendError::ClientNotInitialized) + } - pub fn current_client(&self) -> Result<&Client, BackendError> { - self - .signing_clients - .get(&self.current_network) - .ok_or(BackendError::ClientNotInitialized) - } + pub fn current_client(&self) -> Result<&Client, BackendError> { + self.signing_clients + .get(&self.current_network) + .ok_or(BackendError::ClientNotInitialized) + } - #[allow(unused)] - pub fn current_client_mut(&mut self) -> Result<&mut Client, BackendError> { - self - .signing_clients - .get_mut(&self.current_network) - .ok_or(BackendError::ClientNotInitialized) - } + #[allow(unused)] + pub fn current_client_mut(&mut self) -> Result<&mut Client, BackendError> { + self.signing_clients + .get_mut(&self.current_network) + .ok_or(BackendError::ClientNotInitialized) + } - pub fn config(&self) -> &config::Config { - &self.config - } + pub fn config(&self) -> &config::Config { + &self.config + } - /// Load configuration from files. If unsuccessful we just log it and move on. - pub fn load_config_files(&mut self) { - self.config = config::Config::load_from_files(); - } + /// Load configuration from files. If unsuccessful we just log it and move on. + pub fn load_config_files(&mut self) { + self.config = config::Config::load_from_files(); + } - #[allow(unused)] - pub fn save_config_files(&self) -> Result<(), BackendError> { - Ok(self.config.save_to_files()?) - } + #[allow(unused)] + pub fn save_config_files(&self) -> Result<(), BackendError> { + Ok(self.config.save_to_files()?) + } - pub fn add_client(&mut self, network: Network, client: Client) { - self.signing_clients.insert(network, client); - } + pub fn add_client(&mut self, network: Network, client: Client) { + self.signing_clients.insert(network, client); + } - pub fn set_network(&mut self, network: Network) { - self.current_network = network; - } + pub fn set_network(&mut self, network: Network) { + self.current_network = network; + } - pub fn current_network(&self) -> Network { - self.current_network - } + pub fn current_network(&self) -> Network { + self.current_network + } - pub(crate) fn set_all_accounts(&mut self, all_accounts: Vec) { - self.all_accounts = all_accounts - } + pub(crate) fn set_all_accounts(&mut self, all_accounts: Vec) { + self.all_accounts = all_accounts + } - pub(crate) fn get_all_accounts(&self) -> impl Iterator { - self.all_accounts.iter() - } + pub(crate) fn get_all_accounts(&self) -> impl Iterator { + self.all_accounts.iter() + } - pub fn logout(&mut self) { - self.signing_clients = HashMap::new(); - } + pub fn logout(&mut self) { + self.signing_clients = HashMap::new(); + } - /// Get the available validators in the order - /// 1. from the configuration file - /// 2. provided remotely - /// 3. hardcoded fallback - /// The format is the config backend format, which is flat due to serialization preference. - pub fn get_config_validator_entries( - &self, - network: Network, - ) -> impl Iterator + '_ { - let validators_in_config = self.config.get_configured_validators(network); - let fetched_validators = self.fetched_validators.validators(network).cloned(); - let default_validators = self.config.get_base_validators(network); + /// Get the available validators in the order + /// 1. from the configuration file + /// 2. provided remotely + /// 3. hardcoded fallback + /// The format is the config backend format, which is flat due to serialization preference. + pub fn get_config_validator_entries( + &self, + network: Network, + ) -> impl Iterator + '_ { + let validators_in_config = self.config.get_configured_validators(network); + let fetched_validators = self.fetched_validators.validators(network).cloned(); + let default_validators = self.config.get_base_validators(network); - // All the validators, in decending list of priority - let validators = validators_in_config - .chain(fetched_validators) - .chain(default_validators) - .unique_by(|v| (v.nymd_url.clone(), v.api_url.clone())); + // All the validators, in decending list of priority + let validators = validators_in_config + .chain(fetched_validators) + .chain(default_validators) + .unique_by(|v| (v.nymd_url.clone(), v.api_url.clone())); - // Annotate with dynamic metadata - validators.map(|v| { - let metadata = self.validator_metadata.get(&v.nymd_url); - let name = v - .nymd_name - .or_else(|| metadata.and_then(|m| m.name.clone())); - config::ValidatorConfigEntry { - nymd_url: v.nymd_url, - nymd_name: name, - api_url: v.api_url, - } - }) - } - - pub fn get_nymd_urls_only(&self, network: Network) -> impl Iterator + '_ { - self - .get_config_validator_entries(network) - .into_iter() - .map(|v| v.nymd_url) - } - - pub fn get_api_urls_only(&self, network: Network) -> impl Iterator + '_ { - self - .get_config_validator_entries(network) - .into_iter() - .filter_map(|v| v.api_url) - } - - /// Get the list of validator nymd urls in the network config format, suitable for passing on to - /// the UI - pub fn get_nymd_urls( - &self, - network: Network, - ) -> impl Iterator + '_ { - self - .get_config_validator_entries(network) - .into_iter() - .map(|v| network_config::ValidatorUrl { - url: v.nymd_url.to_string(), - name: v.nymd_name, - }) - } - - /// Get the list of validator-api urls in the network config format, suitable for passing on to - /// the UI - pub fn get_api_urls( - &self, - network: Network, - ) -> impl Iterator + '_ { - self - .get_config_validator_entries(network) - .into_iter() - .filter_map(|v| { - v.api_url.map(|u| network_config::ValidatorUrl { - url: u.to_string(), - name: None, + // Annotate with dynamic metadata + validators.map(|v| { + let metadata = self.validator_metadata.get(&v.nymd_url); + let name = v + .nymd_name + .or_else(|| metadata.and_then(|m| m.name.clone())); + config::ValidatorConfigEntry { + nymd_url: v.nymd_url, + nymd_name: name, + api_url: v.api_url, + } }) - }) - } + } - pub fn get_all_nymd_urls(&self) -> HashMap> { - Network::iter() - .flat_map(|network| { - self - .get_nymd_urls_only(network) - .map(move |url| (network, url)) - }) - .into_group_map() - } + pub fn get_nymd_urls_only(&self, network: Network) -> impl Iterator + '_ { + self.get_config_validator_entries(network) + .into_iter() + .map(|v| v.nymd_url) + } - pub fn get_all_api_urls(&self) -> HashMap> { - Network::iter() - .flat_map(|network| { - self - .get_api_urls_only(network) - .map(move |url| (network, url)) - }) - .into_group_map() - } + pub fn get_api_urls_only(&self, network: Network) -> impl Iterator + '_ { + self.get_config_validator_entries(network) + .into_iter() + .filter_map(|v| v.api_url) + } - /// Fetch validator urls remotely. These are used to in addition to the base ones, and the user - /// configured ones. - pub async fn fetch_updated_validator_urls(&mut self) -> Result<(), BackendError> { - let client = reqwest::Client::builder() - .timeout(Duration::from_secs(3)) - .build()?; - log::debug!( - "Fetching validator urls from: {}", - crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS - ); - let response = client - .get(crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string()) - .send() - .await?; + /// Get the list of validator nymd urls in the network config format, suitable for passing on to + /// the UI + pub fn get_nymd_urls( + &self, + network: Network, + ) -> impl Iterator + '_ { + self.get_config_validator_entries(network) + .into_iter() + .map(|v| network_config::ValidatorUrl { + url: v.nymd_url.to_string(), + name: v.nymd_name, + }) + } - self.fetched_validators = serde_json::from_str(&response.text().await?)?; - log::debug!("Received validator urls: \n{}", self.fetched_validators); + /// Get the list of validator-api urls in the network config format, suitable for passing on to + /// the UI + pub fn get_api_urls( + &self, + network: Network, + ) -> impl Iterator + '_ { + self.get_config_validator_entries(network) + .into_iter() + .filter_map(|v| { + v.api_url.map(|u| network_config::ValidatorUrl { + url: u.to_string(), + name: None, + }) + }) + } - self.refresh_validator_status().await?; + pub fn get_all_nymd_urls(&self) -> HashMap> { + Network::iter() + .flat_map(|network| { + self.get_nymd_urls_only(network) + .map(move |url| (network, url)) + }) + .into_group_map() + } - Ok(()) - } + pub fn get_all_api_urls(&self) -> HashMap> { + Network::iter() + .flat_map(|network| { + self.get_api_urls_only(network) + .map(move |url| (network, url)) + }) + .into_group_map() + } - pub async fn refresh_validator_status(&mut self) -> Result<(), BackendError> { - log::debug!("Refreshing validator status"); - - // All urls for all networks - let nymd_urls = self - .get_all_nymd_urls() - .into_iter() - .flat_map(|(_, urls)| urls.into_iter()); - - // Fetch status for all urls - let responses = fetch_status_for_urls(nymd_urls).await?; - - // Update the stored metadata - self.apply_responses(responses)?; - - // Override some overrides for usability - self.apply_metadata_override(METADATA_OVERRIDES.to_vec()); - - Ok(()) - } - - fn apply_responses( - &mut self, - responses: Vec>, - ) -> Result<(), BackendError> { - for response in responses.into_iter().flatten() { - let json: serde_json::Value = serde_json::from_str(&response.1)?; - let moniker = &json["result"]["node_info"]["moniker"]; - log::debug!("Fetched moniker for: {}: {}", response.0, moniker); - - // Insert into metadata map - if let Some(ref mut m) = self.validator_metadata.get_mut(&response.0) { - m.name = Some(moniker.to_string()); - } else { - self.validator_metadata.insert( - response.0, - ValidatorMetadata { - name: Some(moniker.to_string()), - }, + /// Fetch validator urls remotely. These are used to in addition to the base ones, and the user + /// configured ones. + pub async fn fetch_updated_validator_urls(&mut self) -> Result<(), BackendError> { + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(3)) + .build()?; + log::debug!( + "Fetching validator urls from: {}", + crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS ); - } + let response = client + .get(crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string()) + .send() + .await?; + + self.fetched_validators = serde_json::from_str(&response.text().await?)?; + log::debug!("Received validator urls: \n{}", self.fetched_validators); + + self.refresh_validator_status().await?; + + Ok(()) } - Ok(()) - } - fn apply_metadata_override(&mut self, metadata_overrides: Vec<(Url, ValidatorMetadata)>) { - for (url, metadata) in metadata_overrides { - log::debug!("Overriding (some) metadata for: {url}"); - if let Some(m) = self.validator_metadata.get_mut(&url) { - m.name = metadata.name; - } else { - self.validator_metadata.insert(url, metadata); - } + pub async fn refresh_validator_status(&mut self) -> Result<(), BackendError> { + log::debug!("Refreshing validator status"); + + // All urls for all networks + let nymd_urls = self + .get_all_nymd_urls() + .into_iter() + .flat_map(|(_, urls)| urls.into_iter()); + + // Fetch status for all urls + let responses = fetch_status_for_urls(nymd_urls).await?; + + // Update the stored metadata + self.apply_responses(responses)?; + + // Override some overrides for usability + self.apply_metadata_override(METADATA_OVERRIDES.to_vec()); + + Ok(()) } - } - pub fn select_validator_nymd_url( - &mut self, - url: &str, - network: Network, - ) -> Result<(), BackendError> { - self.config.select_validator_nymd_url(url.parse()?, network); - if let Ok(client) = self.client_mut(network) { - client.change_nymd(url.parse()?)?; + fn apply_responses( + &mut self, + responses: Vec>, + ) -> Result<(), BackendError> { + for response in responses.into_iter().flatten() { + let json: serde_json::Value = serde_json::from_str(&response.1)?; + let moniker = &json["result"]["node_info"]["moniker"]; + log::debug!("Fetched moniker for: {}: {}", response.0, moniker); + + // Insert into metadata map + if let Some(ref mut m) = self.validator_metadata.get_mut(&response.0) { + m.name = Some(moniker.to_string()); + } else { + self.validator_metadata.insert( + response.0, + ValidatorMetadata { + name: Some(moniker.to_string()), + }, + ); + } + } + Ok(()) } - Ok(()) - } - pub fn select_validator_api_url( - &mut self, - url: &str, - network: Network, - ) -> Result<(), BackendError> { - self.config.select_validator_api_url(url.parse()?, network); - if let Ok(client) = self.client_mut(network) { - client.change_validator_api(url.parse()?); + fn apply_metadata_override(&mut self, metadata_overrides: Vec<(Url, ValidatorMetadata)>) { + for (url, metadata) in metadata_overrides { + log::debug!("Overriding (some) metadata for: {url}"); + if let Some(m) = self.validator_metadata.get_mut(&url) { + m.name = metadata.name; + } else { + self.validator_metadata.insert(url, metadata); + } + } } - Ok(()) - } - pub fn add_validator_url(&mut self, url: config::ValidatorConfigEntry, network: Network) { - self.config.add_validator_url(url, network); - } + pub fn select_validator_nymd_url( + &mut self, + url: &str, + network: Network, + ) -> Result<(), BackendError> { + self.config.select_validator_nymd_url(url.parse()?, network); + if let Ok(client) = self.client_mut(network) { + client.change_nymd(url.parse()?)?; + } + Ok(()) + } - pub fn remove_validator_url(&mut self, url: config::ValidatorConfigEntry, network: Network) { - self.config.remove_validator_url(url, network) - } + pub fn select_validator_api_url( + &mut self, + url: &str, + network: Network, + ) -> Result<(), BackendError> { + self.config.select_validator_api_url(url.parse()?, network); + if let Ok(client) = self.client_mut(network) { + client.change_validator_api(url.parse()?); + } + Ok(()) + } + + pub fn add_validator_url(&mut self, url: config::ValidatorConfigEntry, network: Network) { + self.config.add_validator_url(url, network); + } + + pub fn remove_validator_url(&mut self, url: config::ValidatorConfigEntry, network: Network) { + self.config.remove_validator_url(url, network) + } } async fn fetch_status_for_urls( - nymd_urls: impl Iterator, + nymd_urls: impl Iterator, ) -> Result>, BackendError> { - let client = reqwest::Client::builder() - .timeout(Duration::from_secs(3)) - .build()?; + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(3)) + .build()?; - let responses = futures::future::join_all(nymd_urls.into_iter().map(|url| { - let client = &client; - let status_url = url.join("status").unwrap_or_else(|_| url.clone()); - async move { - let resp = client.get(status_url).send().await?; - resp.text().await.map(|text| (url, text)) - } - })) - .await; + let responses = futures::future::join_all(nymd_urls.into_iter().map(|url| { + let client = &client; + let status_url = url.join("status").unwrap_or_else(|_| url.clone()); + async move { + let resp = client.get(status_url).send().await?; + resp.text().await.map(|text| (url, text)) + } + })) + .await; - Ok(responses) + Ok(responses) } // Validator metadata that can by dynamically populated #[derive(Clone, Debug)] pub struct ValidatorMetadata { - pub name: Option, + pub name: Option, } #[macro_export] macro_rules! client { - ($state:ident) => { - $state.read().await.current_client()? - }; + ($state:ident) => { + $state.read().await.current_client()? + }; } #[macro_export] macro_rules! nymd_client { - ($state:ident) => { - $state.read().await.current_client()?.nymd - }; + ($state:ident) => { + $state.read().await.current_client()?.nymd + }; } #[macro_export] macro_rules! api_client { - ($state:ident) => { - $state.read().await.current_client()?.validator_api - }; + ($state:ident) => { + $state.read().await.current_client()?.validator_api + }; } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn adding_validators_urls_prepends() { - let mut state = State::default(); - let _api_urls = state.get_api_urls(Network::MAINNET).collect::>(); + #[test] + fn adding_validators_urls_prepends() { + let mut state = State::default(); + let _api_urls = state.get_api_urls(Network::MAINNET).collect::>(); - state.add_validator_url( - config::ValidatorConfigEntry { - nymd_url: "http://nymd_url.com".parse().unwrap(), - nymd_name: Some("NymdUrl".to_string()), - api_url: Some("http://nymd_url.com/api".parse().unwrap()), - }, - Network::MAINNET, - ); + state.add_validator_url( + config::ValidatorConfigEntry { + nymd_url: "http://nymd_url.com".parse().unwrap(), + nymd_name: Some("NymdUrl".to_string()), + api_url: Some("http://nymd_url.com/api".parse().unwrap()), + }, + Network::MAINNET, + ); - state.add_validator_url( - config::ValidatorConfigEntry { - nymd_url: "http://foo.com".parse().unwrap(), - nymd_name: None, - api_url: None, - }, - Network::MAINNET, - ); + state.add_validator_url( + config::ValidatorConfigEntry { + nymd_url: "http://foo.com".parse().unwrap(), + nymd_name: None, + api_url: None, + }, + Network::MAINNET, + ); - state.add_validator_url( - config::ValidatorConfigEntry { - nymd_url: "http://bar.com".parse().unwrap(), - nymd_name: None, - api_url: None, - }, - Network::MAINNET, - ); + state.add_validator_url( + config::ValidatorConfigEntry { + nymd_url: "http://bar.com".parse().unwrap(), + nymd_name: None, + api_url: None, + }, + Network::MAINNET, + ); - assert_eq!( - state - .get_nymd_urls_only(Network::MAINNET) - .collect::>(), - vec![ - "http://nymd_url.com/".parse().unwrap(), - "http://foo.com".parse().unwrap(), - "http://bar.com".parse().unwrap(), - "https://rpc.nyx.nodes.guru".parse().unwrap(), - ], - ); - assert_eq!( - state - .get_api_urls_only(Network::MAINNET) - .collect::>(), - vec![ - "http://nymd_url.com/api".parse().unwrap(), - "https://validator.nymtech.net/api/".parse().unwrap(), - ], - ); - assert_eq!( - state - .get_all_nymd_urls() - .get(&Network::MAINNET) - .unwrap() - .clone(), - vec![ - "http://nymd_url.com/".parse().unwrap(), - "http://foo.com".parse().unwrap(), - "http://bar.com".parse().unwrap(), - "https://rpc.nyx.nodes.guru".parse().unwrap(), - ], - ) - } + assert_eq!( + state + .get_nymd_urls_only(Network::MAINNET) + .collect::>(), + vec![ + "http://nymd_url.com/".parse().unwrap(), + "http://foo.com".parse().unwrap(), + "http://bar.com".parse().unwrap(), + "https://rpc.nyx.nodes.guru".parse().unwrap(), + ], + ); + assert_eq!( + state + .get_api_urls_only(Network::MAINNET) + .collect::>(), + vec![ + "http://nymd_url.com/api".parse().unwrap(), + "https://validator.nymtech.net/api/".parse().unwrap(), + ], + ); + assert_eq!( + state + .get_all_nymd_urls() + .get(&Network::MAINNET) + .unwrap() + .clone(), + vec![ + "http://nymd_url.com/".parse().unwrap(), + "http://foo.com".parse().unwrap(), + "http://bar.com".parse().unwrap(), + "https://rpc.nyx.nodes.guru".parse().unwrap(), + ], + ) + } } diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index 9a28277e3e..405da42ee3 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -14,126 +14,128 @@ use tokio::sync::RwLock; #[cfg_attr(test, ts(export, export_to = "../src/types/rust/appEnv.ts"))] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct AppEnv { - pub ADMIN_ADDRESS: Option, - pub SHOW_TERMINAL: Option, + pub ADMIN_ADDRESS: Option, + pub SHOW_TERMINAL: Option, } fn get_env_as_option(key: &str) -> Option { - match ::std::env::var(key) { - Ok(res) => Some(res), - Err(_e) => None, - } + match ::std::env::var(key) { + Ok(res) => Some(res), + Err(_e) => None, + } } #[tauri::command] pub fn get_env() -> AppEnv { - AppEnv { - ADMIN_ADDRESS: get_env_as_option("ADMIN_ADDRESS"), - SHOW_TERMINAL: get_env_as_option("SHOW_TERMINAL"), - } + AppEnv { + ADMIN_ADDRESS: get_env_as_option("ADMIN_ADDRESS"), + SHOW_TERMINAL: get_env_as_option("SHOW_TERMINAL"), + } } #[tauri::command] pub fn major_to_minor(amount: &str) -> Coin { - let coin = Coin::new(amount, &Denom::Major); - coin.to_minor() + let coin = Coin::new(amount, &Denom::Major); + coin.to_minor() } #[tauri::command] pub fn minor_to_major(amount: &str) -> Coin { - let coin = Coin::new(amount, &Denom::Minor); - coin.to_major() + let coin = Coin::new(amount, &Denom::Minor); + coin.to_major() } #[tauri::command] pub async fn owns_mixnode( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .owns_mixnode(nymd_client!(state).address()) - .await? - .is_some(), - ) + Ok(nymd_client!(state) + .owns_mixnode(nymd_client!(state).address()) + .await? + .is_some()) } #[tauri::command] pub async fn owns_gateway( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, Arc>>, ) -> Result { - Ok( - nymd_client!(state) - .owns_gateway(nymd_client!(state).address()) - .await? - .is_some(), - ) + Ok(nymd_client!(state) + .owns_gateway(nymd_client!(state).address()) + .await? + .is_some()) } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/delegationresult.ts"))] #[derive(Serialize, Deserialize)] pub struct DelegationResult { - source_address: String, - target_address: String, - amount: Option, + source_address: String, + target_address: String, + amount: Option, } impl DelegationResult { - pub fn new(source_address: &str, target_address: &str, amount: Option) -> DelegationResult { - DelegationResult { - source_address: source_address.to_string(), - target_address: target_address.to_string(), - amount, + pub fn new( + source_address: &str, + target_address: &str, + amount: Option, + ) -> DelegationResult { + DelegationResult { + source_address: source_address.to_string(), + target_address: target_address.to_string(), + amount, + } } - } } impl From for DelegationResult { - fn from(delegation: Delegation) -> Self { - DelegationResult { - source_address: delegation.owner().to_string(), - target_address: delegation.node_identity(), - amount: Some(delegation.amount.into()), + fn from(delegation: Delegation) -> Self { + DelegationResult { + source_address: delegation.owner().to_string(), + target_address: delegation.node_identity(), + amount: Some(delegation.amount.into()), + } } - } } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/delegationevent.ts"))] #[derive(Deserialize, Serialize)] pub enum DelegationEvent { - Delegate(DelegationResult), - Undelegate(PendingUndelegate), + Delegate(DelegationResult), + Undelegate(PendingUndelegate), } impl From for DelegationEvent { - fn from(event: ContractDelegationEvent) -> Self { - match event { - ContractDelegationEvent::Delegate(delegation) => DelegationEvent::Delegate(delegation.into()), - ContractDelegationEvent::Undelegate(pending_undelegate) => { - DelegationEvent::Undelegate(pending_undelegate.into()) - } + fn from(event: ContractDelegationEvent) -> Self { + match event { + ContractDelegationEvent::Delegate(delegation) => { + DelegationEvent::Delegate(delegation.into()) + } + ContractDelegationEvent::Undelegate(pending_undelegate) => { + DelegationEvent::Undelegate(pending_undelegate.into()) + } + } } - } } #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/pendingundelegate.ts"))] #[derive(Deserialize, Serialize)] pub struct PendingUndelegate { - mix_identity: String, - delegate: String, - proxy: Option, - block_height: u64, + mix_identity: String, + delegate: String, + proxy: Option, + block_height: u64, } impl From for PendingUndelegate { - fn from(pending_undelegate: ContractPendingUndelegate) -> Self { - PendingUndelegate { - mix_identity: pending_undelegate.mix_identity(), - delegate: pending_undelegate.delegate().to_string(), - proxy: pending_undelegate.proxy().map(|p| p.to_string()), - block_height: pending_undelegate.block_height(), + fn from(pending_undelegate: ContractPendingUndelegate) -> Self { + PendingUndelegate { + mix_identity: pending_undelegate.mix_identity(), + delegate: pending_undelegate.delegate().to_string(), + proxy: pending_undelegate.proxy().map(|p| p.to_string()), + block_height: pending_undelegate.block_height(), + } } - } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 10149948e9..d0027592d9 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -29,119 +29,125 @@ const CURRENT_WALLET_FILE_VERSION: u32 = 1; /// The wallet, stored as a serialized json file. #[derive(Serialize, Deserialize, Debug)] pub(crate) struct StoredWallet { - version: u32, - accounts: Vec, + version: u32, + accounts: Vec, } impl StoredWallet { - #[allow(unused)] - pub fn version(&self) -> u32 { - self.version - } - - #[allow(unused)] - pub fn len(&self) -> usize { - self.accounts.len() - } - - pub fn is_empty(&self) -> bool { - self.accounts.is_empty() - } - - pub fn add_encrypted_login(&mut self, new_login: EncryptedLogin) -> Result<(), BackendError> { - if self.get_encrypted_login(&new_login.id).is_ok() { - return Err(BackendError::WalletLoginIdAlreadyExists); + #[allow(unused)] + pub fn version(&self) -> u32 { + self.version } - self.accounts.push(new_login); - Ok(()) - } - fn get_encrypted_login(&self, id: &LoginId) -> Result<&EncryptedData, BackendError> { - self - .accounts - .iter() - .find(|account| &account.id == id) - .map(|account| &account.account) - .ok_or(BackendError::WalletNoSuchLoginId) - } - - fn get_encrypted_login_mut(&mut self, id: &LoginId) -> Result<&mut EncryptedLogin, BackendError> { - self - .accounts - .iter_mut() - .find(|account| &account.id == id) - .ok_or(BackendError::WalletNoSuchLoginId) - } - - #[cfg(test)] - pub fn get_encrypted_login_by_index(&self, index: usize) -> Option<&EncryptedLogin> { - self.accounts.get(index) - } - - pub fn replace_encrypted_login(&mut self, new_login: EncryptedLogin) -> Result<(), BackendError> { - let login = self.get_encrypted_login_mut(&new_login.id)?; - *login = new_login; - Ok(()) - } - - pub fn remove_encrypted_login(&mut self, id: &LoginId) -> Option { - if let Some(index) = self.accounts.iter().position(|account| &account.id == id) { - log::info!("Removing from wallet file: {id}"); - Some(self.accounts.remove(index)) - } else { - log::debug!("Tried to remove non-existent id from wallet: {id}"); - None + #[allow(unused)] + pub fn len(&self) -> usize { + self.accounts.len() } - } - pub fn decrypt_login( - &self, - id: &LoginId, - password: &UserPassword, - ) -> Result { - self.get_encrypted_login(id)?.decrypt_struct(password) - } + pub fn is_empty(&self) -> bool { + self.accounts.is_empty() + } - pub fn decrypt_all(&self, password: &UserPassword) -> Result, BackendError> { - self - .accounts - .iter() - .map(|account| account.account.decrypt_struct(password)) - .collect::, _>>() - } + pub fn add_encrypted_login(&mut self, new_login: EncryptedLogin) -> Result<(), BackendError> { + if self.get_encrypted_login(&new_login.id).is_ok() { + return Err(BackendError::WalletLoginIdAlreadyExists); + } + self.accounts.push(new_login); + Ok(()) + } - pub fn password_can_decrypt_all(&self, password: &UserPassword) -> bool { - self.decrypt_all(password).is_ok() - } + fn get_encrypted_login( + &self, + id: &LoginId, + ) -> Result<&EncryptedData, BackendError> { + self.accounts + .iter() + .find(|account| &account.id == id) + .map(|account| &account.account) + .ok_or(BackendError::WalletNoSuchLoginId) + } + + fn get_encrypted_login_mut( + &mut self, + id: &LoginId, + ) -> Result<&mut EncryptedLogin, BackendError> { + self.accounts + .iter_mut() + .find(|account| &account.id == id) + .ok_or(BackendError::WalletNoSuchLoginId) + } + + #[cfg(test)] + pub fn get_encrypted_login_by_index(&self, index: usize) -> Option<&EncryptedLogin> { + self.accounts.get(index) + } + + pub fn replace_encrypted_login( + &mut self, + new_login: EncryptedLogin, + ) -> Result<(), BackendError> { + let login = self.get_encrypted_login_mut(&new_login.id)?; + *login = new_login; + Ok(()) + } + + pub fn remove_encrypted_login(&mut self, id: &LoginId) -> Option { + if let Some(index) = self.accounts.iter().position(|account| &account.id == id) { + log::info!("Removing from wallet file: {id}"); + Some(self.accounts.remove(index)) + } else { + log::debug!("Tried to remove non-existent id from wallet: {id}"); + None + } + } + + pub fn decrypt_login( + &self, + id: &LoginId, + password: &UserPassword, + ) -> Result { + self.get_encrypted_login(id)?.decrypt_struct(password) + } + + pub fn decrypt_all(&self, password: &UserPassword) -> Result, BackendError> { + self.accounts + .iter() + .map(|account| account.account.decrypt_struct(password)) + .collect::, _>>() + } + + pub fn password_can_decrypt_all(&self, password: &UserPassword) -> bool { + self.decrypt_all(password).is_ok() + } } impl Default for StoredWallet { - fn default() -> Self { - StoredWallet { - version: CURRENT_WALLET_FILE_VERSION, - accounts: Vec::new(), + fn default() -> Self { + StoredWallet { + version: CURRENT_WALLET_FILE_VERSION, + accounts: Vec::new(), + } } - } } /// Each entry in the stored wallet file. An id field in plaintext and an encrypted stored login. #[derive(Serialize, Deserialize, Debug)] pub(crate) struct EncryptedLogin { - pub id: LoginId, - pub account: EncryptedData, + pub id: LoginId, + pub account: EncryptedData, } impl EncryptedLogin { - pub(crate) fn encrypt( - id: LoginId, - login: &StoredLogin, - password: &UserPassword, - ) -> Result { - Ok(EncryptedLogin { - id, - account: super::encryption::encrypt_struct(login, password)?, - }) - } + pub(crate) fn encrypt( + id: LoginId, + login: &StoredLogin, + password: &UserPassword, + ) -> Result { + Ok(EncryptedLogin { + id, + account: super::encryption::encrypt_struct(login, password)?, + }) + } } /// A stored login is either a account, such as a mnemonic, or a list of multiple accounts where @@ -150,147 +156,148 @@ impl EncryptedLogin { #[serde(untagged)] #[zeroize(drop)] pub(crate) enum StoredLogin { - Mnemonic(MnemonicAccount), - // PrivateKey(PrivateKeyAccount) - Multiple(MultipleAccounts), + Mnemonic(MnemonicAccount), + // PrivateKey(PrivateKeyAccount) + Multiple(MultipleAccounts), } impl StoredLogin { - #[cfg(test)] - pub(crate) fn as_mnemonic_account(&self) -> Option<&MnemonicAccount> { - match self { - StoredLogin::Mnemonic(mn) => Some(mn), - StoredLogin::Multiple(_) => None, + #[cfg(test)] + pub(crate) fn as_mnemonic_account(&self) -> Option<&MnemonicAccount> { + match self { + StoredLogin::Mnemonic(mn) => Some(mn), + StoredLogin::Multiple(_) => None, + } } - } - #[cfg(test)] - pub(crate) fn as_multiple_accounts(&self) -> Option<&MultipleAccounts> { - match self { - StoredLogin::Mnemonic(_) => None, - StoredLogin::Multiple(accounts) => Some(accounts), + #[cfg(test)] + pub(crate) fn as_multiple_accounts(&self) -> Option<&MultipleAccounts> { + match self { + StoredLogin::Mnemonic(_) => None, + StoredLogin::Multiple(accounts) => Some(accounts), + } } - } - // Return the login as multiple accounts, and if there is only a single mnemonic backed account, - // return a set containing only the single account paired with the account id passed as function - // argument. - pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { - match self { - StoredLogin::Mnemonic(ref account) => vec![WalletAccount::new(id, account.clone())].into(), - StoredLogin::Multiple(ref accounts) => accounts.clone(), + // Return the login as multiple accounts, and if there is only a single mnemonic backed account, + // return a set containing only the single account paired with the account id passed as function + // argument. + pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { + match self { + StoredLogin::Mnemonic(ref account) => { + vec![WalletAccount::new(id, account.clone())].into() + } + StoredLogin::Multiple(ref accounts) => accounts.clone(), + } } - } } /// Multiple stored accounts, each entry having an id and a data field. #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct MultipleAccounts { - accounts: Vec, + accounts: Vec, } impl MultipleAccounts { - pub(crate) fn new() -> Self { - MultipleAccounts { - accounts: Vec::new(), + pub(crate) fn new() -> Self { + MultipleAccounts { + accounts: Vec::new(), + } } - } - pub(crate) fn get_accounts(&self) -> impl Iterator { - self.accounts.iter() - } - - pub(crate) fn get_account(&self, id: &AccountId) -> Option<&WalletAccount> { - self.accounts.iter().find(|account| &account.id == id) - } - - pub(crate) fn get_account_with_mnemonic( - &self, - mnemonic: &bip39::Mnemonic, - ) -> Option<&WalletAccount> { - self - .get_accounts() - .find(|account| account.mnemonic() == mnemonic) - } - - pub(crate) fn into_accounts(self) -> impl Iterator { - self.accounts.into_iter() - } - - #[allow(unused)] - pub(crate) fn len(&self) -> usize { - self.accounts.len() - } - - pub(crate) fn is_empty(&self) -> bool { - self.accounts.is_empty() - } - - pub(crate) fn add( - &mut self, - id: AccountId, - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - ) -> Result<(), BackendError> { - if self.get_account(&id).is_some() { - Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin) - } else if self.get_account_with_mnemonic(&mnemonic).is_some() { - Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin) - } else { - self.accounts.push(WalletAccount::new( - id, - MnemonicAccount::new(mnemonic, hd_path), - )); - Ok(()) + pub(crate) fn get_accounts(&self) -> impl Iterator { + self.accounts.iter() } - } - pub(crate) fn remove(&mut self, id: &AccountId) -> Result<(), BackendError> { - if self.get_account(id).is_none() { - return Err(BackendError::WalletNoSuchAccountIdInWalletLogin); + pub(crate) fn get_account(&self, id: &AccountId) -> Option<&WalletAccount> { + self.accounts.iter().find(|account| &account.id == id) + } + + pub(crate) fn get_account_with_mnemonic( + &self, + mnemonic: &bip39::Mnemonic, + ) -> Option<&WalletAccount> { + self.get_accounts() + .find(|account| account.mnemonic() == mnemonic) + } + + pub(crate) fn into_accounts(self) -> impl Iterator { + self.accounts.into_iter() + } + + #[allow(unused)] + pub(crate) fn len(&self) -> usize { + self.accounts.len() + } + + pub(crate) fn is_empty(&self) -> bool { + self.accounts.is_empty() + } + + pub(crate) fn add( + &mut self, + id: AccountId, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + ) -> Result<(), BackendError> { + if self.get_account(&id).is_some() { + Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin) + } else if self.get_account_with_mnemonic(&mnemonic).is_some() { + Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin) + } else { + self.accounts.push(WalletAccount::new( + id, + MnemonicAccount::new(mnemonic, hd_path), + )); + Ok(()) + } + } + + pub(crate) fn remove(&mut self, id: &AccountId) -> Result<(), BackendError> { + if self.get_account(id).is_none() { + return Err(BackendError::WalletNoSuchAccountIdInWalletLogin); + } + self.accounts.retain(|accounts| &accounts.id != id); + Ok(()) } - self.accounts.retain(|accounts| &accounts.id != id); - Ok(()) - } } impl From> for MultipleAccounts { - fn from(accounts: Vec) -> MultipleAccounts { - Self { accounts } - } + fn from(accounts: Vec) -> MultipleAccounts { + Self { accounts } + } } /// An entry in the list of stored accounts #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct WalletAccount { - id: AccountId, - account: AccountData, + id: AccountId, + account: AccountData, } impl WalletAccount { - pub(crate) fn new(id: AccountId, mnemonic_account: MnemonicAccount) -> Self { - Self { - id, - account: AccountData::Mnemonic(mnemonic_account), + pub(crate) fn new(id: AccountId, mnemonic_account: MnemonicAccount) -> Self { + Self { + id, + account: AccountData::Mnemonic(mnemonic_account), + } } - } - pub(crate) fn id(&self) -> &AccountId { - &self.id - } - - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { - match self.account { - AccountData::Mnemonic(ref account) => account.mnemonic(), + pub(crate) fn id(&self) -> &AccountId { + &self.id } - } - #[cfg(test)] - pub(crate) fn hd_path(&self) -> &DerivationPath { - match self.account { - AccountData::Mnemonic(ref account) => account.hd_path(), + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { + match self.account { + AccountData::Mnemonic(ref account) => account.mnemonic(), + } + } + + #[cfg(test)] + pub(crate) fn hd_path(&self) -> &DerivationPath { + match self.account { + AccountData::Mnemonic(ref account) => account.hd_path(), + } } - } } /// An account usually is a mnemonic account, but in the future it might be backed by a private @@ -299,69 +306,69 @@ impl WalletAccount { #[serde(untagged)] #[zeroize(drop)] enum AccountData { - Mnemonic(MnemonicAccount), - // PrivateKey(PrivateKeyAccount) + Mnemonic(MnemonicAccount), + // PrivateKey(PrivateKeyAccount) } /// An account backed by a unique mnemonic. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct MnemonicAccount { - mnemonic: bip39::Mnemonic, - #[serde(with = "display_hd_path")] - hd_path: DerivationPath, + mnemonic: bip39::Mnemonic, + #[serde(with = "display_hd_path")] + hd_path: DerivationPath, } impl MnemonicAccount { - pub(crate) fn new(mnemonic: bip39::Mnemonic, hd_path: DerivationPath) -> Self { - Self { mnemonic, hd_path } - } + pub(crate) fn new(mnemonic: bip39::Mnemonic, hd_path: DerivationPath) -> Self { + Self { mnemonic, hd_path } + } - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { - &self.mnemonic - } + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { + &self.mnemonic + } - #[cfg(test)] - pub(crate) fn hd_path(&self) -> &DerivationPath { - &self.hd_path - } + #[cfg(test)] + pub(crate) fn hd_path(&self) -> &DerivationPath { + &self.hd_path + } } impl Zeroize for MnemonicAccount { - fn zeroize(&mut self) { - // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) - // and the memory would have been filled with zeroes. - // - // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing - // of overwriting it with a fresh mnemonic that was never used before - // - // note: this function can only fail on an invalid word count, which clearly is not the case here - self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap(); + fn zeroize(&mut self) { + // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) + // and the memory would have been filled with zeroes. + // + // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing + // of overwriting it with a fresh mnemonic that was never used before + // + // note: this function can only fail on an invalid word count, which clearly is not the case here + self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap(); - // further note: we don't really care about the hd_path, there's nothing secret about it. - } + // further note: we don't really care about the hd_path, there's nothing secret about it. + } } impl Drop for MnemonicAccount { - fn drop(&mut self) { - self.zeroize() - } + fn drop(&mut self) { + self.zeroize() + } } mod display_hd_path { - use serde::{Deserialize, Deserializer, Serializer}; - use validator_client::nymd::bip32::DerivationPath; + use serde::{Deserialize, Deserializer, Serializer}; + use validator_client::nymd::bip32::DerivationPath; - pub fn serialize( - hd_path: &DerivationPath, - serializer: S, - ) -> Result { - serializer.collect_str(hd_path) - } + pub fn serialize( + hd_path: &DerivationPath, + serializer: S, + ) -> Result { + serializer.collect_str(hd_path) + } - pub fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result { - let s = <&str>::deserialize(deserializer)?; - s.parse().map_err(serde::de::Error::custom) - } + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + let s = <&str>::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/encryption.rs b/nym-wallet/src-tauri/src/wallet_storage/encryption.rs index 101dde0417..bfaea8582e 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/encryption.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/encryption.rs @@ -7,8 +7,8 @@ use aes_gcm::aead::generic_array::ArrayLength; use aes_gcm::aead::{Aead, NewAead}; use aes_gcm::{Aes256Gcm, Key, Nonce}; use argon2::{ - password_hash::rand_core::{OsRng, RngCore}, - Algorithm, Argon2, Params, Version, + password_hash::rand_core::{OsRng, RngCore}, + Algorithm, Argon2, Params, Version, }; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; @@ -27,249 +27,249 @@ const IV_LEN: usize = 12; #[derive(Debug, Serialize, Deserialize, Zeroize)] pub(crate) struct EncryptedData { - #[serde(with = "base64")] - ciphertext: Vec, - #[serde(with = "base64")] - salt: Vec, - #[serde(with = "base64")] - iv: Vec, + #[serde(with = "base64")] + ciphertext: Vec, + #[serde(with = "base64")] + salt: Vec, + #[serde(with = "base64")] + iv: Vec, - #[serde(skip)] - #[zeroize(skip)] - _marker: PhantomData, + #[serde(skip)] + #[zeroize(skip)] + _marker: PhantomData, } impl Drop for EncryptedData { - fn drop(&mut self) { - self.zeroize(); - } + fn drop(&mut self) { + self.zeroize(); + } } // we only ever want to expose those getters in the test code #[cfg(test)] impl EncryptedData { - pub(crate) fn ciphertext(&self) -> &[u8] { - &self.ciphertext - } + pub(crate) fn ciphertext(&self) -> &[u8] { + &self.ciphertext + } - pub(crate) fn salt(&self) -> &[u8] { - &self.salt - } + pub(crate) fn salt(&self) -> &[u8] { + &self.salt + } - pub(crate) fn iv(&self) -> &[u8] { - &self.iv - } + pub(crate) fn iv(&self) -> &[u8] { + &self.iv + } } // helper to make Vec serialization use base64 representation to make it human readable // so that it would be easier for users to copy contents from the disk if they wanted to use it elsewhere mod base64 { - use serde::{Deserialize, Deserializer, Serializer}; + use serde::{Deserialize, Deserializer, Serializer}; - pub fn serialize(bytes: &[u8], serializer: S) -> Result { - serializer.serialize_str(&base64::encode(bytes)) - } + pub fn serialize(bytes: &[u8], serializer: S) -> Result { + serializer.serialize_str(&base64::encode(bytes)) + } - pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { - let s = ::deserialize(deserializer)?; - base64::decode(&s).map_err(serde::de::Error::custom) - } + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let s = ::deserialize(deserializer)?; + base64::decode(&s).map_err(serde::de::Error::custom) + } } impl EncryptedData { - #[allow(unused)] - pub(crate) fn encrypt_struct(data: &T, password: &UserPassword) -> Result - where - T: Serialize, - { - encrypt_struct(data, password) - } + #[allow(unused)] + pub(crate) fn encrypt_struct(data: &T, password: &UserPassword) -> Result + where + T: Serialize, + { + encrypt_struct(data, password) + } - pub(crate) fn decrypt_struct(&self, password: &UserPassword) -> Result - where - T: for<'a> Deserialize<'a>, - { - decrypt_struct(self, password) - } + pub(crate) fn decrypt_struct(&self, password: &UserPassword) -> Result + where + T: for<'a> Deserialize<'a>, + { + decrypt_struct(self, password) + } } impl EncryptedData> { - #[allow(unused)] - pub(crate) fn encrypt_data(data: &[u8], password: &UserPassword) -> Result { - encrypt_data(data, password) - } + #[allow(unused)] + pub(crate) fn encrypt_data(data: &[u8], password: &UserPassword) -> Result { + encrypt_data(data, password) + } - #[allow(unused)] - pub(crate) fn decrypt_data(&self, password: &UserPassword) -> Result, BackendError> { - decrypt_data(self, password) - } + #[allow(unused)] + pub(crate) fn decrypt_data(&self, password: &UserPassword) -> Result, BackendError> { + decrypt_data(self, password) + } } fn derive_cipher_key( - password: &UserPassword, - salt: &[u8], + password: &UserPassword, + salt: &[u8], ) -> Result, BackendError> where - KeySize: ArrayLength, + KeySize: ArrayLength, { - // this can only fail if output length is either smaller than 4 or larger than 2^32 - 1 which is not the case here - let params = Params::new(MEMORY_COST, ITERATIONS, PARALLELISM, Some(OUTPUT_LENGTH)).unwrap(); + // this can only fail if output length is either smaller than 4 or larger than 2^32 - 1 which is not the case here + let params = Params::new(MEMORY_COST, ITERATIONS, PARALLELISM, Some(OUTPUT_LENGTH)).unwrap(); - let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); + let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); - let mut key = Key::default(); - argon2.hash_password_into(password.as_bytes(), salt, &mut key)?; + let mut key = Key::default(); + argon2.hash_password_into(password.as_bytes(), salt, &mut key)?; - Ok(key) + Ok(key) } fn random_salt_and_iv() -> (Vec, Vec) { - let mut rng = OsRng; + let mut rng = OsRng; - let mut salt = vec![0u8; SALT_LEN]; - rng.fill_bytes(&mut salt); + let mut salt = vec![0u8; SALT_LEN]; + rng.fill_bytes(&mut salt); - let mut iv = vec![0u8; IV_LEN]; - rng.fill_bytes(&mut iv); + let mut iv = vec![0u8; IV_LEN]; + rng.fill_bytes(&mut iv); - (salt, iv) + (salt, iv) } fn encrypt( - data: &[u8], - password: &UserPassword, - salt: &[u8], - iv: &[u8], + data: &[u8], + password: &UserPassword, + salt: &[u8], + iv: &[u8], ) -> Result, BackendError> { - let key = derive_cipher_key(password, salt)?; - let cipher = Aes256Gcm::new(&key); - cipher - .encrypt(Nonce::from_slice(iv), data) - .map_err(|_| BackendError::EncryptionError) + let key = derive_cipher_key(password, salt)?; + let cipher = Aes256Gcm::new(&key); + cipher + .encrypt(Nonce::from_slice(iv), data) + .map_err(|_| BackendError::EncryptionError) } fn decrypt( - ciphertext: &[u8], - password: &UserPassword, - salt: &[u8], - iv: &[u8], + ciphertext: &[u8], + password: &UserPassword, + salt: &[u8], + iv: &[u8], ) -> Result, BackendError> { - let key = derive_cipher_key(password, salt)?; - let cipher = Aes256Gcm::new(&key); - cipher - .decrypt(Nonce::from_slice(iv), ciphertext) - .map_err(|_| BackendError::DecryptionError) + let key = derive_cipher_key(password, salt)?; + let cipher = Aes256Gcm::new(&key); + cipher + .decrypt(Nonce::from_slice(iv), ciphertext) + .map_err(|_| BackendError::DecryptionError) } #[allow(unused)] pub(crate) fn encrypt_data( - data: &[u8], - password: &UserPassword, + data: &[u8], + password: &UserPassword, ) -> Result>, BackendError> { - let (salt, iv) = random_salt_and_iv(); - let ciphertext = encrypt(data, password, &salt, &iv)?; + let (salt, iv) = random_salt_and_iv(); + let ciphertext = encrypt(data, password, &salt, &iv)?; - Ok(EncryptedData { - ciphertext, - salt, - iv, - _marker: Default::default(), - }) + Ok(EncryptedData { + ciphertext, + salt, + iv, + _marker: Default::default(), + }) } pub(crate) fn encrypt_struct( - data: &T, - password: &UserPassword, + data: &T, + password: &UserPassword, ) -> Result, BackendError> where - T: Serialize, + T: Serialize, { - let bytes = serde_json::to_vec(data).map_err(|_| BackendError::EncryptionError)?; + let bytes = serde_json::to_vec(data).map_err(|_| BackendError::EncryptionError)?; - let (salt, iv) = random_salt_and_iv(); - let ciphertext = encrypt(&bytes, password, &salt, &iv)?; + let (salt, iv) = random_salt_and_iv(); + let ciphertext = encrypt(&bytes, password, &salt, &iv)?; - Ok(EncryptedData { - ciphertext, - salt, - iv, - _marker: Default::default(), - }) + Ok(EncryptedData { + ciphertext, + salt, + iv, + _marker: Default::default(), + }) } #[allow(unused)] pub(crate) fn decrypt_data( - encrypted_data: &EncryptedData>, - password: &UserPassword, + encrypted_data: &EncryptedData>, + password: &UserPassword, ) -> Result, BackendError> { - decrypt( - &encrypted_data.ciphertext, - password, - &encrypted_data.salt, - &encrypted_data.iv, - ) + decrypt( + &encrypted_data.ciphertext, + password, + &encrypted_data.salt, + &encrypted_data.iv, + ) } pub(crate) fn decrypt_struct( - encrypted_data: &EncryptedData, - password: &UserPassword, + encrypted_data: &EncryptedData, + password: &UserPassword, ) -> Result where - T: for<'a> Deserialize<'a>, + T: for<'a> Deserialize<'a>, { - let bytes = decrypt( - &encrypted_data.ciphertext, - password, - &encrypted_data.salt, - &encrypted_data.iv, - )?; + let bytes = decrypt( + &encrypted_data.ciphertext, + password, + &encrypted_data.salt, + &encrypted_data.iv, + )?; - serde_json::from_slice(&bytes).map_err(|_| BackendError::DecryptionError) + serde_json::from_slice(&bytes).map_err(|_| BackendError::DecryptionError) } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct DummyData { - foo: String, - bar: String, - } + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct DummyData { + foo: String, + bar: String, + } - #[test] - fn struct_encryption() { - let password = UserPassword::new("my-super-secret-password".to_string()); - let data = DummyData { - foo: "my secret mnemonic".to_string(), - bar: "totally-valid-hd-path".to_string(), - }; + #[test] + fn struct_encryption() { + let password = UserPassword::new("my-super-secret-password".to_string()); + let data = DummyData { + foo: "my secret mnemonic".to_string(), + bar: "totally-valid-hd-path".to_string(), + }; - let wrong_password = UserPassword::new("brute-force-attempt-1".to_string()); + let wrong_password = UserPassword::new("brute-force-attempt-1".to_string()); - let mut encrypted_data = encrypt_struct(&data, &password).unwrap(); - let recovered = decrypt_struct(&encrypted_data, &password).unwrap(); - assert_eq!(data, recovered); + let mut encrypted_data = encrypt_struct(&data, &password).unwrap(); + let recovered = decrypt_struct(&encrypted_data, &password).unwrap(); + assert_eq!(data, recovered); - // decryption with wrong password fails - assert!(decrypt_struct(&encrypted_data, &wrong_password).is_err()); + // decryption with wrong password fails + assert!(decrypt_struct(&encrypted_data, &wrong_password).is_err()); - // decryption fails if ciphertext got malformed - encrypted_data.ciphertext[3] ^= 123; - assert!(decrypt_struct(&encrypted_data, &wrong_password).is_err()); + // decryption fails if ciphertext got malformed + encrypted_data.ciphertext[3] ^= 123; + assert!(decrypt_struct(&encrypted_data, &wrong_password).is_err()); - // restore the ciphertext (for test purposes) - encrypted_data.ciphertext[3] ^= 123; + // restore the ciphertext (for test purposes) + encrypted_data.ciphertext[3] ^= 123; - // decryption fails if salt got malformed (it would result in incorrect key being derived) - encrypted_data.salt[3] ^= 123; - assert!(decrypt_struct(&encrypted_data, &password).is_err()); + // decryption fails if salt got malformed (it would result in incorrect key being derived) + encrypted_data.salt[3] ^= 123; + assert!(decrypt_struct(&encrypted_data, &password).is_err()); - // restore the salt (for test purposes) - encrypted_data.salt[3] ^= 123; + // restore the salt (for test purposes) + encrypted_data.salt[3] ^= 123; - // decryption fails if iv got malformed - encrypted_data.iv[3] ^= 123; - assert!(decrypt_struct(&encrypted_data, &password).is_err()); - } + // decryption fails if iv got malformed + encrypted_data.iv[3] ^= 123; + assert!(decrypt_struct(&encrypted_data, &password).is_err()); + } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 9427471ff9..5fcc56bc1e 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -35,146 +35,146 @@ pub(crate) const DEFAULT_LOGIN_ID: &str = "default"; pub(crate) const DEFAULT_FIRST_ACCOUNT_NAME: &str = "Account 1"; fn get_storage_directory() -> Result { - tauri::api::path::local_data_dir() - .map(|dir| dir.join(STORAGE_DIR_NAME)) - .ok_or(BackendError::UnknownStorageDirectory) + tauri::api::path::local_data_dir() + .map(|dir| dir.join(STORAGE_DIR_NAME)) + .ok_or(BackendError::UnknownStorageDirectory) } pub(crate) fn wallet_login_filepath() -> Result { - get_storage_directory().map(|dir| dir.join(WALLET_INFO_FILENAME)) + get_storage_directory().map(|dir| dir.join(WALLET_INFO_FILENAME)) } fn write_to_file(filepath: &Path, wallet: &StoredWallet) -> Result<(), BackendError> { - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(filepath)?; + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(filepath)?; - Ok(serde_json::to_writer_pretty(file, &wallet)?) + Ok(serde_json::to_writer_pretty(file, &wallet)?) } /// Load stored wallet file #[allow(unused)] pub(crate) fn load_existing_wallet() -> Result { - let store_dir = get_storage_directory()?; - let filepath = store_dir.join(WALLET_INFO_FILENAME); - load_existing_wallet_at_file(&filepath) + let store_dir = get_storage_directory()?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + load_existing_wallet_at_file(&filepath) } fn load_existing_wallet_at_file(filepath: &Path) -> Result { - if !filepath.exists() { - return Err(BackendError::WalletFileNotFound); - } - let file = OpenOptions::new().read(true).open(filepath)?; - let wallet: StoredWallet = serde_json::from_reader(file)?; - Ok(wallet) + if !filepath.exists() { + return Err(BackendError::WalletFileNotFound); + } + let file = OpenOptions::new().read(true).open(filepath)?; + let wallet: StoredWallet = serde_json::from_reader(file)?; + Ok(wallet) } /// Load the stored wallet file and return the stored login for the given id. /// The returned login is either an account or list of (inner id, account) pairs. pub(crate) fn load_existing_login( - id: &LoginId, - password: &UserPassword, + id: &LoginId, + password: &UserPassword, ) -> Result { - let store_dir = get_storage_directory()?; - let filepath = store_dir.join(WALLET_INFO_FILENAME); - load_existing_login_at_file(&filepath, id, password) + let store_dir = get_storage_directory()?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + load_existing_login_at_file(&filepath, id, password) } pub(crate) fn load_existing_login_at_file( - filepath: &Path, - id: &LoginId, - password: &UserPassword, + filepath: &Path, + id: &LoginId, + password: &UserPassword, ) -> Result { - load_existing_wallet_at_file(filepath)?.decrypt_login(id, password) + load_existing_wallet_at_file(filepath)?.decrypt_login(id, password) } // DEPRECATED: only used in tests, where it's used to test supporting older wallet formats #[allow(unused)] #[cfg(test)] pub(crate) fn store_login( - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - id: LoginId, - password: &UserPassword, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: LoginId, + password: &UserPassword, ) -> Result<(), BackendError> { - // make sure the entire directory structure exists - let store_dir = get_storage_directory()?; - create_dir_all(&store_dir)?; - let filepath = store_dir.join(WALLET_INFO_FILENAME); + // make sure the entire directory structure exists + let store_dir = get_storage_directory()?; + create_dir_all(&store_dir)?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_login_at_file(&filepath, mnemonic, hd_path, id, password) + store_login_at_file(&filepath, mnemonic, hd_path, id, password) } // DEPRECATED: only used in tests, where it's used to test supporting older wallet formats #[cfg(test)] fn store_login_at_file( - filepath: &Path, - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - id: LoginId, - password: &UserPassword, + filepath: &Path, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: LoginId, + password: &UserPassword, ) -> Result<(), BackendError> { - let mut stored_wallet = match load_existing_wallet_at_file(filepath) { - Err(BackendError::WalletFileNotFound) => StoredWallet::default(), - result => result?, - }; + let mut stored_wallet = match load_existing_wallet_at_file(filepath) { + Err(BackendError::WalletFileNotFound) => StoredWallet::default(), + result => result?, + }; - // Confirm that the given password also can unlock the other entries. - // This is restriction we can relax in the future, but for now it's a sanity check. - if !stored_wallet.password_can_decrypt_all(password) { - return Err(BackendError::WalletDifferentPasswordDetected); - } + // Confirm that the given password also can unlock the other entries. + // This is restriction we can relax in the future, but for now it's a sanity check. + if !stored_wallet.password_can_decrypt_all(password) { + return Err(BackendError::WalletDifferentPasswordDetected); + } - let new_account = MnemonicAccount::new(mnemonic, hd_path); - let new_login = StoredLogin::Mnemonic(new_account); - let new_encrypted_account = EncryptedLogin::encrypt(id, &new_login, password)?; - stored_wallet.add_encrypted_login(new_encrypted_account)?; + let new_account = MnemonicAccount::new(mnemonic, hd_path); + let new_login = StoredLogin::Mnemonic(new_account); + let new_encrypted_account = EncryptedLogin::encrypt(id, &new_login, password)?; + stored_wallet.add_encrypted_login(new_encrypted_account)?; - write_to_file(filepath, &stored_wallet) + write_to_file(filepath, &stored_wallet) } pub(crate) fn store_login_with_multiple_accounts( - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - id: LoginId, - password: &UserPassword, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: LoginId, + password: &UserPassword, ) -> Result<(), BackendError> { - // make sure the entire directory structure exists - let store_dir = get_storage_directory()?; - create_dir_all(&store_dir)?; - let filepath = store_dir.join(WALLET_INFO_FILENAME); + // make sure the entire directory structure exists + let store_dir = get_storage_directory()?; + create_dir_all(&store_dir)?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_login_with_multiple_accounts_at_file(&filepath, mnemonic, hd_path, id, password) + store_login_with_multiple_accounts_at_file(&filepath, mnemonic, hd_path, id, password) } fn store_login_with_multiple_accounts_at_file( - filepath: &Path, - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - id: LoginId, - password: &UserPassword, + filepath: &Path, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: LoginId, + password: &UserPassword, ) -> Result<(), BackendError> { - let mut stored_wallet = match load_existing_wallet_at_file(filepath) { - Err(BackendError::WalletFileNotFound) => StoredWallet::default(), - result => result?, - }; + let mut stored_wallet = match load_existing_wallet_at_file(filepath) { + Err(BackendError::WalletFileNotFound) => StoredWallet::default(), + result => result?, + }; - // Confirm that the given password also can unlock the other entries. - // This is restriction we can relax in the future, but for now it's a sanity check. - if !stored_wallet.password_can_decrypt_all(password) { - return Err(BackendError::WalletDifferentPasswordDetected); - } + // Confirm that the given password also can unlock the other entries. + // This is restriction we can relax in the future, but for now it's a sanity check. + if !stored_wallet.password_can_decrypt_all(password) { + return Err(BackendError::WalletDifferentPasswordDetected); + } - let mut new_accounts = MultipleAccounts::new(); - new_accounts.add(DEFAULT_FIRST_ACCOUNT_NAME.into(), mnemonic, hd_path)?; - let new_login = StoredLogin::Multiple(new_accounts); - let new_encrypted_login = EncryptedLogin::encrypt(id, &new_login, password)?; + let mut new_accounts = MultipleAccounts::new(); + new_accounts.add(DEFAULT_FIRST_ACCOUNT_NAME.into(), mnemonic, hd_path)?; + let new_login = StoredLogin::Multiple(new_accounts); + let new_encrypted_login = EncryptedLogin::encrypt(id, &new_login, password)?; - stored_wallet.add_encrypted_login(new_encrypted_login)?; + stored_wallet.add_encrypted_login(new_encrypted_login)?; - write_to_file(filepath, &stored_wallet) + write_to_file(filepath, &stored_wallet) } /// Append an account to an already existing top-level encrypted account entry. @@ -182,74 +182,75 @@ fn store_login_with_multiple_accounts_at_file( /// account in the list of accounts associated with the encrypted entry. The inner id for this /// entry will be set to the same as the outer, unencrypted, id. pub(crate) fn append_account_to_login( - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - id: LoginId, - inner_id: AccountId, - password: &UserPassword, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: LoginId, + inner_id: AccountId, + password: &UserPassword, ) -> Result<(), BackendError> { - // make sure the entire directory structure exists - let store_dir = get_storage_directory()?; - create_dir_all(&store_dir)?; - let filepath = store_dir.join(WALLET_INFO_FILENAME); + // make sure the entire directory structure exists + let store_dir = get_storage_directory()?; + create_dir_all(&store_dir)?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); - append_account_to_login_at_file(&filepath, mnemonic, hd_path, id, inner_id, password) + append_account_to_login_at_file(&filepath, mnemonic, hd_path, id, inner_id, password) } fn append_account_to_login_at_file( - filepath: &Path, - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - id: LoginId, - inner_id: AccountId, - password: &UserPassword, + filepath: &Path, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: LoginId, + inner_id: AccountId, + password: &UserPassword, ) -> Result<(), BackendError> { - let mut stored_wallet = load_existing_wallet_at_file(filepath)?; + let mut stored_wallet = load_existing_wallet_at_file(filepath)?; - let decrypted_login = stored_wallet.decrypt_login(&id, password)?; + let decrypted_login = stored_wallet.decrypt_login(&id, password)?; - // Add accounts to the inner structure. - // Note that in case we only have single account entry, without an inner_id, we convert to - // multiple accounts and we set the first inner_id to id. - let first_id_when_converting = id.clone().into(); - let mut accounts = decrypted_login.unwrap_into_multiple_accounts(first_id_when_converting); - accounts.add(inner_id, mnemonic, hd_path)?; + // Add accounts to the inner structure. + // Note that in case we only have single account entry, without an inner_id, we convert to + // multiple accounts and we set the first inner_id to id. + let first_id_when_converting = id.clone().into(); + let mut accounts = decrypted_login.unwrap_into_multiple_accounts(first_id_when_converting); + accounts.add(inner_id, mnemonic, hd_path)?; - let encrypted_accounts = EncryptedLogin::encrypt(id, &StoredLogin::Multiple(accounts), password)?; + let encrypted_accounts = + EncryptedLogin::encrypt(id, &StoredLogin::Multiple(accounts), password)?; - stored_wallet.replace_encrypted_login(encrypted_accounts)?; + stored_wallet.replace_encrypted_login(encrypted_accounts)?; - write_to_file(filepath, &stored_wallet) + write_to_file(filepath, &stored_wallet) } /// Remove the entire encrypted login entry for the given `id`. This means potentially removing all /// associated accounts! /// If this was the last entry in the file, the file is removed. pub(crate) fn remove_login(id: &LoginId) -> Result<(), BackendError> { - let store_dir = get_storage_directory()?; - let filepath = store_dir.join(WALLET_INFO_FILENAME); - remove_login_at_file(&filepath, id) + let store_dir = get_storage_directory()?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + remove_login_at_file(&filepath, id) } fn remove_login_at_file(filepath: &Path, id: &LoginId) -> Result<(), BackendError> { - log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); - let mut stored_wallet = load_existing_wallet_at_file(filepath)?; + log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); + let mut stored_wallet = load_existing_wallet_at_file(filepath)?; - if stored_wallet.is_empty() { - log::info!("Removing file: {:#?}", filepath); - return Ok(fs::remove_file(filepath)?); - } + if stored_wallet.is_empty() { + log::info!("Removing file: {:#?}", filepath); + return Ok(fs::remove_file(filepath)?); + } - stored_wallet - .remove_encrypted_login(id) - .ok_or(BackendError::WalletNoSuchLoginId)?; + stored_wallet + .remove_encrypted_login(id) + .ok_or(BackendError::WalletNoSuchLoginId)?; - if stored_wallet.is_empty() { - log::info!("Removing file: {:#?}", filepath); - Ok(fs::remove_file(filepath)?) - } else { - write_to_file(filepath, &stored_wallet) - } + if stored_wallet.is_empty() { + log::info!("Removing file: {:#?}", filepath); + Ok(fs::remove_file(filepath)?) + } else { + write_to_file(filepath, &stored_wallet) + } } /// Remove an account from inside the encrypted login. @@ -257,1267 +258,1283 @@ fn remove_login_at_file(filepath: &Path, id: &LoginId) -> Result<(), BackendErro /// - If it is the last associated account with that login, the encrypted login will be removed. /// - If this was the last encrypted login in the file, it will be removed. pub(crate) fn remove_account_from_login( - id: &LoginId, - inner_id: &AccountId, - password: &UserPassword, + id: &LoginId, + inner_id: &AccountId, + password: &UserPassword, ) -> Result<(), BackendError> { - let store_dir = get_storage_directory()?; - let filepath = store_dir.join(WALLET_INFO_FILENAME); - remove_account_from_login_at_file(&filepath, id, inner_id, password) + let store_dir = get_storage_directory()?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + remove_account_from_login_at_file(&filepath, id, inner_id, password) } fn remove_account_from_login_at_file( - filepath: &Path, - id: &LoginId, - inner_id: &AccountId, - password: &UserPassword, + filepath: &Path, + id: &LoginId, + inner_id: &AccountId, + password: &UserPassword, ) -> Result<(), BackendError> { - log::info!("Removing associated account from login account: {id}"); - let mut stored_wallet = load_existing_wallet_at_file(filepath)?; + log::info!("Removing associated account from login account: {id}"); + let mut stored_wallet = load_existing_wallet_at_file(filepath)?; - let mut decrypted_login = stored_wallet.decrypt_login(id, password)?; + let mut decrypted_login = stored_wallet.decrypt_login(id, password)?; - // Remove the account - let is_empty = match decrypted_login { - StoredLogin::Mnemonic(_) => { - log::warn!("Encountered mnemonic login instead of list of accounts, aborting"); - return Err(BackendError::WalletUnexpectedMnemonicAccount); + // Remove the account + let is_empty = match decrypted_login { + StoredLogin::Mnemonic(_) => { + log::warn!("Encountered mnemonic login instead of list of accounts, aborting"); + return Err(BackendError::WalletUnexpectedMnemonicAccount); + } + StoredLogin::Multiple(ref mut accounts) => { + accounts.remove(inner_id)?; + accounts.is_empty() + } + }; + + // Remove the login, or encrypt the new updated login + if is_empty { + stored_wallet + .remove_encrypted_login(id) + .ok_or(BackendError::WalletNoSuchLoginId)?; + } else { + let encrypted_accounts = EncryptedLogin::encrypt(id.clone(), &decrypted_login, password)?; + stored_wallet.replace_encrypted_login(encrypted_accounts)?; } - StoredLogin::Multiple(ref mut accounts) => { - accounts.remove(inner_id)?; - accounts.is_empty() + + // Remove the file, or write the new file + if stored_wallet.is_empty() { + log::info!("Removing file: {:#?}", filepath); + Ok(fs::remove_file(filepath)?) + } else { + write_to_file(filepath, &stored_wallet) } - }; - - // Remove the login, or encrypt the new updated login - if is_empty { - stored_wallet - .remove_encrypted_login(id) - .ok_or(BackendError::WalletNoSuchLoginId)?; - } else { - let encrypted_accounts = EncryptedLogin::encrypt(id.clone(), &decrypted_login, password)?; - stored_wallet.replace_encrypted_login(encrypted_accounts)?; - } - - // Remove the file, or write the new file - if stored_wallet.is_empty() { - log::info!("Removing file: {:#?}", filepath); - Ok(fs::remove_file(filepath)?) - } else { - write_to_file(filepath, &stored_wallet) - } } #[cfg(test)] mod tests { - use crate::wallet_storage::account_data::{MnemonicAccount, WalletAccount}; - - use super::*; - use config::defaults::COSMOS_DERIVATION_PATH; - use std::str::FromStr; - use tempfile::tempdir; - - #[test] - fn trying_to_load_nonexistant_wallet_fails() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let id1 = LoginId::new("first".to_string()); - let password = UserPassword::new("password".to_string()); - - assert!(matches!( - load_existing_wallet_at_file(&wallet_file), - Err(BackendError::WalletFileNotFound), - )); - assert!(matches!( - load_existing_login_at_file(&wallet_file, &id1, &password), - Err(BackendError::WalletFileNotFound), - )); - remove_login_at_file(&wallet_file, &id1).unwrap_err(); - } - - #[test] - fn store_single_login() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - - store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); - - let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); - assert_eq!(stored_wallet.len(), 1); - - let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); - assert_eq!(login.id, id1); - - // some actual ciphertext was saved - assert!(!login.account.ciphertext().is_empty()); - } - - #[test] - fn store_single_login_with_multi() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - account1, - cosmos_hd_path, - id1.clone(), - &password, - ) - .unwrap(); - - let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); - assert_eq!(stored_wallet.len(), 1); - - let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); - assert_eq!(login.id, id1); - - // some actual ciphertext was saved - assert!(!login.account.ciphertext().is_empty()); - } - - #[test] - fn store_twice_for_the_same_id_fails() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - - // Store the first login - store_login_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - // and storing the same id again fails - assert!(matches!( - store_login_at_file(&wallet_file, account1, hd_path, id1, &password,), - Err(BackendError::WalletLoginIdAlreadyExists), - )); - } - - #[test] - fn store_twice_for_the_same_id_fails_with_multiple() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - - // Store the first login - store_login_with_multiple_accounts_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - // and storing the same id again fails - assert!(matches!( - store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password,), - Err(BackendError::WalletLoginIdAlreadyExists), - )); - } - - #[test] - fn load_with_wrong_password_fails() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = LoginId::new("first".to_string()); - - store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); - - // Trying to load it with wrong password now fails - assert!(matches!( - load_existing_login_at_file(&wallet_file, &id1, &bad_password), - Err(BackendError::DecryptionError), - )); - } - - #[test] - fn load_with_wrong_password_fails_with_multi() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = LoginId::new("first".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - account1, - hd_path, - id1.clone(), - &password, - ) - .unwrap(); - - // Trying to load it with wrong password now fails - assert!(matches!( - load_existing_login_at_file(&wallet_file, &id1, &bad_password), - Err(BackendError::DecryptionError), - )); - } - - #[test] - fn load_with_wrong_id_fails() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); - - // Trying to load with the wrong id - assert!(matches!( - load_existing_login_at_file(&wallet_file, &id2, &password), - Err(BackendError::WalletNoSuchLoginId), - )); - } - - #[test] - fn load_with_wrong_id_fails_with_multi() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password) - .unwrap(); - - // Trying to load with the wrong id - assert!(matches!( - load_existing_login_at_file(&wallet_file, &id2, &password), - Err(BackendError::WalletNoSuchLoginId), - )); - } - - #[test] - fn store_and_load_a_single_login() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - - store_login_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); - assert_eq!(&hd_path, acc.hd_path()); - } - - #[test] - fn store_and_load_a_single_login_with_multi() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - acc1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let accounts = loaded_login.as_multiple_accounts().unwrap(); - assert_eq!(accounts.len(), 1); - let account = accounts - .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) - .unwrap(); - assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &acc1); - assert_eq!(account.hd_path(), &hd_path); - } - - #[test] - fn store_a_second_login_with_a_different_password_fails() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - store_login_at_file( - &wallet_file, - account1, - cosmos_hd_path.clone(), - id1, - &password, - ) - .unwrap(); - - // Can't store a second login if you use different password - assert!(matches!( - store_login_at_file(&wallet_file, account2, cosmos_hd_path, id2, &bad_password), - Err(BackendError::WalletDifferentPasswordDetected), - )); - } - - #[test] - fn store_a_second_login_with_a_different_password_fails_with_multi() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - account1, - hd_path.clone(), - id1, - &password, - ) - .unwrap(); - - // Can't store a second login if you use different password - assert!(matches!( - store_login_with_multiple_accounts_at_file( - &wallet_file, - account2, - hd_path, - id2, - &bad_password - ), - Err(BackendError::WalletDifferentPasswordDetected), - )); - } - - #[test] - fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - // Store the first account - store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); - - let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); - let encrypted_blob = &stored_wallet - .get_encrypted_login_by_index(0) - .unwrap() - .account; - - // keep track of salt and iv for future assertion - let original_iv = encrypted_blob.iv().to_vec(); - let original_salt = encrypted_blob.salt().to_vec(); - - // Add an extra account - store_login_at_file(&wallet_file, account2, different_hd_path, id2, &password).unwrap(); - - let loaded_accounts = load_existing_wallet_at_file(&wallet_file).unwrap(); - assert_eq!(loaded_accounts.len(), 2); - let encrypted_blob = &loaded_accounts - .get_encrypted_login_by_index(1) - .unwrap() - .account; - - // fresh IV and salt are used - assert_ne!(original_iv, encrypted_blob.iv()); - assert_ne!(original_salt, encrypted_blob.salt()); - } - - #[test] - fn store_two_mnemonic_accounts_using_two_logins() { - let store_dir = tempdir().unwrap(); - let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - // Store the first account - store_login_at_file( - &wallet, - account1.clone(), - cosmos_hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - let login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); - let acc = login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); - assert_eq!(&cosmos_hd_path, acc.hd_path()); - - // Add an extra account - store_login_at_file( - &wallet, - account2.clone(), - different_hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // first account should be unchanged - let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); - let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); - - let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); - let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account2, acc2.mnemonic()); - assert_eq!(&different_hd_path, acc2.hd_path()); - } - - #[test] - fn store_one_mnemonic_account_and_one_multi_account() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - // Store the first account - store_login_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); - assert_eq!(&hd_path, acc.hd_path()); - - // Add an extra account - store_login_with_multiple_accounts_at_file( - &wallet_file, - account2.clone(), - different_hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // first account should be unchanged - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); - assert_eq!(&hd_path, acc1.hd_path()); - - let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); - let acc2 = loaded_login.as_multiple_accounts().unwrap(); - assert_eq!(acc2.len(), 1); - let account = acc2 - .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) - .unwrap(); - assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &account2); - assert_eq!(account.hd_path(), &different_hd_path); - } - - #[test] - fn remove_non_existent_id_fails() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password) - .unwrap(); - - // Fails to delete non-existent id in the wallet - assert!(matches!( - remove_login_at_file(&wallet_file, &id2), - Err(BackendError::WalletNoSuchLoginId), - )); - } - - #[test] - fn store_and_remove_wallet_login_information() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - // Store two accounts with two different passwords - store_login_at_file( - &wallet_file, - account1.clone(), - cosmos_hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - store_login_at_file( - &wallet_file, - account2.clone(), - different_hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // Load and compare - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); - - let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); - let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account2, acc2.mnemonic()); - assert_eq!(&different_hd_path, acc2.hd_path()); - - // Delete the second account - remove_login_at_file(&wallet_file, &id2).unwrap(); - - // The first account should be unchanged - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); - - // And we can't load the second one anymore - assert!(matches!( - load_existing_login_at_file(&wallet_file, &id2, &password), - Err(BackendError::WalletNoSuchLoginId), - )); - - // Delete the first account - assert!(wallet_file.exists()); - remove_login_at_file(&wallet_file, &id1).unwrap(); - - // The file should now be removed - assert!(!wallet_file.exists()); - - // And trying to load when the file is gone fails - assert!(matches!( - load_existing_login_at_file(&wallet_file, &id1, &password), - Err(BackendError::WalletFileNotFound), - )); - } - - #[test] - fn append_account_converts_the_type() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - - store_login_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - // Check that it's there as the correct non-multiple type - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc.mnemonic(), &account1); - assert_eq!(acc.hd_path(), &hd_path); - - append_account_to_login_at_file( - &wallet_file, - account2.clone(), - hd_path.clone(), - id1.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // Check that it is now multiple mnemonic type - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![ - WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())), - WalletAccount::new(id2, MnemonicAccount::new(account2, hd_path)), - ] - .into(); - assert_eq!(loaded_accounts, &expected); - } - - #[test] - fn append_accounts_to_existing_login() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let account4 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - let id3 = AccountId::new("third".to_string()); - let id4 = AccountId::new("fourth".to_string()); - - store_login_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - store_login_at_file( - &wallet_file, - account2.clone(), - hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // Check that it's there as the correct non-multiple type - let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); - let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc2.mnemonic(), &account2); - assert_eq!(acc2.hd_path(), &hd_path); - - // Add a third and fourth mnenonic grouped together with the second one - append_account_to_login_at_file( - &wallet_file, - account3.clone(), - hd_path.clone(), - id2.clone(), - id3.clone(), - &password, - ) - .unwrap(); - append_account_to_login_at_file( - &wallet_file, - account4.clone(), - hd_path.clone(), - id2.clone(), - id4.clone(), - &password, - ) - .unwrap(); - - // Check that we can load all four - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &account1); - assert_eq!(acc1.hd_path(), &hd_path); - - let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); - let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![ - WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), - ] - .into(); - assert_eq!(loaded_accounts, &expected); - } - - #[test] - fn append_accounts_to_existing_login_with_multi() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let account4 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - let id3 = AccountId::new("third".to_string()); - let id4 = AccountId::new("fourth".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - account2.clone(), - hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // Add a third and fourth mnenonic grouped together with the second one - append_account_to_login_at_file( - &wallet_file, - account3.clone(), - hd_path.clone(), - id2.clone(), - id3.clone(), - &password, - ) - .unwrap(); - append_account_to_login_at_file( - &wallet_file, - account4.clone(), - hd_path.clone(), - id2.clone(), - id4.clone(), - &password, - ) - .unwrap(); - - // Check that we can load all four - let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); - let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![WalletAccount::new( - DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account1, hd_path.clone()), - )] - .into(); - assert_eq!(loaded_accounts, &expected); - - let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); - let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![ - WalletAccount::new( - DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account2, hd_path.clone()), - ), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), - ] - .into(); - assert_eq!(loaded_accounts, &expected); - } - - #[test] - fn append_the_same_mnemonic_twice_fails() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - account1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - assert!(matches!( - append_account_to_login_at_file(&wallet_file, account1, hd_path, id1, id2, &password,), - Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin), - )) - } - - #[test] - fn delete_the_same_account_twice_for_a_login_fails() { - let store_dir = tempdir().unwrap(); - let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - - store_login_at_file(&wallet, account1, hd_path.clone(), id1.clone(), &password).unwrap(); - - append_account_to_login_at_file( - &wallet, - account2, - hd_path, - id1.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); - - assert!(matches!( - remove_account_from_login_at_file(&wallet, &id1, &id2, &password), - Err(BackendError::WalletNoSuchAccountIdInWalletLogin), - )); - } - - #[test] - fn delete_the_same_account_twice_for_a_login_fails_with_multi() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet_file, - account1, - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - append_account_to_login_at_file( - &wallet_file, - account2, - hd_path, - id1.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - remove_account_from_login_at_file(&wallet_file, &id1, &id2, &password).unwrap(); - - assert!(matches!( - remove_account_from_login_at_file(&wallet_file, &id1, &id2, &password), - Err(BackendError::WalletNoSuchAccountIdInWalletLogin), - )); - } - - #[test] - fn delete_appended_account_doesnt_affect_others() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - let id3 = AccountId::new("third".to_string()); - - store_login_at_file( - &wallet_file, - account1, - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - store_login_at_file( - &wallet_file, - account2.clone(), - hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - append_account_to_login_at_file( - &wallet_file, - account3.clone(), - hd_path.clone(), - id2.clone(), - id3.clone(), - &password, - ) - .unwrap(); - - remove_login_at_file(&wallet_file, &id1).unwrap(); - - // The second login one is still there - let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); - let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![ - WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path)), - ] - .into(); - assert_eq!(loaded_accounts, &expected); - } - - #[test] - fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { - let store_dir = tempdir().unwrap(); - let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet, - account1, - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - append_account_to_login_at_file( - &wallet, - account2, - hd_path, - id1.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - remove_account_from_login_at_file(&wallet, &id1, &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password) - .unwrap(); - remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); - - // The file should now be removed - assert!(!wallet.exists()); - - // And trying to load when the file is gone fails - assert!(matches!( - load_existing_login_at_file(&wallet, &id1, &password), - Err(BackendError::WalletFileNotFound), - )); - } - - #[test] - fn remove_all_accounts_for_a_login_removes_that_login() { - let store_dir = tempdir().unwrap(); - let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - let id3 = LoginId::new("third".to_string()); - - store_login_with_multiple_accounts_at_file( - &wallet, - account1, - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - append_account_to_login_at_file( - &wallet, - account2, - hd_path.clone(), - id1.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - store_login_with_multiple_accounts_at_file( - &wallet, - account3.clone(), - hd_path.clone(), - id3.clone(), - &password, - ) - .unwrap(); - - remove_account_from_login_at_file(&wallet, &id1, &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password) - .unwrap(); - remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); - - // And trying to load when the file is gone fails - assert!(matches!( - load_existing_login_at_file(&wallet, &id1, &password), - Err(BackendError::WalletNoSuchLoginId), - )); - - // The other login is still there - let loaded_login = load_existing_login_at_file(&wallet, &id3, &password).unwrap(); - let acc3 = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![WalletAccount::new( - DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account3, hd_path), - )] - .into(); - assert_eq!(acc3, &expected); - } - - #[test] - fn append_accounts_and_remove_appended_accounts() { - let store_dir = tempdir().unwrap(); - let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); - let acc2 = bip39::Mnemonic::generate(24).unwrap(); - let acc3 = bip39::Mnemonic::generate(24).unwrap(); - let acc4 = bip39::Mnemonic::generate(24).unwrap(); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - let id3 = AccountId::new("third".to_string()); - let id4 = AccountId::new("fourth".to_string()); - - store_login_at_file( - &wallet, - acc1.clone(), - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - store_login_at_file( - &wallet, - acc2.clone(), - hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // Add a third and fourth mnenonic grouped together with the second one - append_account_to_login_at_file( - &wallet, - acc3, - hd_path.clone(), - id2.clone(), - id3.clone(), - &password, - ) - .unwrap(); - append_account_to_login_at_file( - &wallet, - acc4.clone(), - hd_path.clone(), - id2.clone(), - id4.clone(), - &password, - ) - .unwrap(); - - // Delete the third mnemonic, from the second login entry - remove_account_from_login_at_file(&wallet, &id2, &id3, &password).unwrap(); - - // Check that we can still load the other accounts - let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); - let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![ - WalletAccount::new( - id2.clone().into(), - MnemonicAccount::new(acc2, hd_path.clone()), - ), - WalletAccount::new(id4.clone(), MnemonicAccount::new(acc4, hd_path.clone())), - ] - .into(); - assert_eq!(loaded_accounts, &expected); - - // Delete the second and fourth mnemonic from the second login entry removes the login entry - remove_account_from_login_at_file(&wallet, &id2, &id2.clone().into(), &password).unwrap(); - remove_account_from_login_at_file(&wallet, &id2, &id4, &password).unwrap(); - assert!(matches!( - load_existing_login_at_file(&wallet, &id2, &password), - Err(BackendError::WalletNoSuchLoginId), - )); - - // The first login is still available - let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); - let account = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(account.mnemonic(), &acc1); - assert_eq!(account.hd_path(), &hd_path); - } - - // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored - // wallets created with older versions. - #[test] - fn decrypt_stored_wallet() { - const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; - let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); - - let wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); - - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = LoginId::new("first".to_string()); - let id2 = LoginId::new("second".to_string()); - - assert!(!wallet.password_can_decrypt_all(&bad_password)); - assert!(wallet.password_can_decrypt_all(&password)); - - let acc1 = wallet.decrypt_login(&id1, &password).unwrap(); - let acc2 = wallet.decrypt_login(&id2, &password).unwrap(); - - assert!(matches!(acc1, StoredLogin::Mnemonic(_))); - assert!(matches!(acc2, StoredLogin::Mnemonic(_))); - - let expected_acc1 = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); - let expected_acc2 = bip39::Mnemonic::from_str("home mansion start quiz dress decide hint second dragon sunny juice always steak real minimum art rival skin draw total pulp foot goddess agent").unwrap(); - - assert_eq!( - acc1.as_mnemonic_account().unwrap().mnemonic(), - &expected_acc1 - ); - assert_eq!(acc1.as_mnemonic_account().unwrap().hd_path(), &hd_path,); - - assert_eq!( - acc2.as_mnemonic_account().unwrap().mnemonic(), - &expected_acc2 - ); - assert_eq!(acc2.as_mnemonic_account().unwrap().hd_path(), &hd_path,); - } - - #[test] - fn decrypt_stored_wallet_1_0_4() { - const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet-1.0.4.json"; - let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); - - let wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); - - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password11!".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); - let login_id = LoginId::new("default".to_string()); - - assert!(!wallet.password_can_decrypt_all(&bad_password)); - assert!(wallet.password_can_decrypt_all(&password)); - - let acc1 = wallet.decrypt_login(&login_id, &password).unwrap(); - - assert!(matches!(acc1, StoredLogin::Mnemonic(_))); - - let expected_acc1 = bip39::Mnemonic::from_str("arrow capable abstract industry elevator nominee december piece hotel feed lounge web faint sword veteran bundle hour page actual laptop horror gold test warrior").unwrap(); - - assert_eq!( - acc1.as_mnemonic_account().unwrap().mnemonic(), - &expected_acc1 - ); - assert_eq!(acc1.as_mnemonic_account().unwrap().hd_path(), &hd_path,); - } - - #[test] - fn decrypt_stored_wallet_1_0_5_with_multiple_accounts() { - const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet-1.0.5.json"; - let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); - - let wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); - - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password11!".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); - let login_id = LoginId::new("default".to_string()); - - assert!(!wallet.password_can_decrypt_all(&bad_password)); - assert!(wallet.password_can_decrypt_all(&password)); - - let login = wallet.decrypt_login(&login_id, &password).unwrap(); - - assert!(matches!(login, StoredLogin::Multiple(_))); - - let login = login.as_multiple_accounts().unwrap(); - assert_eq!(login.len(), 4); - - let expected_mn1 = bip39::Mnemonic::from_str("arrow capable abstract industry elevator nominee december piece hotel feed lounge web faint sword veteran bundle hour page actual laptop horror gold test warrior").unwrap(); - let expected_mn2 = bip39::Mnemonic::from_str("border hurt skull lunar goddess second danger game dismiss exhaust oven thumb dog drama onion false orchard spice tent next predict invite cherry green").unwrap(); - let expected_mn3 = bip39::Mnemonic::from_str("gentle crowd rule snap girl urge flat jump winner cluster night sand museum stock grunt quick tree acquire traffic major awake tag rack peasant").unwrap(); - let expected_mn4 = bip39::Mnemonic::from_str("debris blue skin annual inhale text border rigid spatial lesson coconut yard horn crystal control survey version vote hawk neck frame arrive oblige width").unwrap(); - - let expected = vec![ - WalletAccount::new( - "default".into(), - MnemonicAccount::new(expected_mn1, hd_path.clone()), - ), - WalletAccount::new( - "account2".into(), - MnemonicAccount::new(expected_mn2, hd_path.clone()), - ), - WalletAccount::new( - "foobar".into(), - MnemonicAccount::new(expected_mn3, hd_path.clone()), - ), - WalletAccount::new("42".into(), MnemonicAccount::new(expected_mn4, hd_path)), - ] - .into(); - - assert_eq!(login, &expected); - } + use crate::wallet_storage::account_data::{MnemonicAccount, WalletAccount}; + + use super::*; + use config::defaults::COSMOS_DERIVATION_PATH; + use std::str::FromStr; + use tempfile::tempdir; + + #[test] + fn trying_to_load_nonexistant_wallet_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let id1 = LoginId::new("first".to_string()); + let password = UserPassword::new("password".to_string()); + + assert!(matches!( + load_existing_wallet_at_file(&wallet_file), + Err(BackendError::WalletFileNotFound), + )); + assert!(matches!( + load_existing_login_at_file(&wallet_file, &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + remove_login_at_file(&wallet_file, &id1).unwrap_err(); + } + + #[test] + fn store_single_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + + store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); + + let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); + assert_eq!(stored_wallet.len(), 1); + + let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); + assert_eq!(login.id, id1); + + // some actual ciphertext was saved + assert!(!login.account.ciphertext().is_empty()); + } + + #[test] + fn store_single_login_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1, + cosmos_hd_path, + id1.clone(), + &password, + ) + .unwrap(); + + let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); + assert_eq!(stored_wallet.len(), 1); + + let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); + assert_eq!(login.id, id1); + + // some actual ciphertext was saved + assert!(!login.account.ciphertext().is_empty()); + } + + #[test] + fn store_twice_for_the_same_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + + // Store the first login + store_login_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // and storing the same id again fails + assert!(matches!( + store_login_at_file(&wallet_file, account1, hd_path, id1, &password,), + Err(BackendError::WalletLoginIdAlreadyExists), + )); + } + + #[test] + fn store_twice_for_the_same_id_fails_with_multiple() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + + // Store the first login + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // and storing the same id again fails + assert!(matches!( + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1, + hd_path, + id1, + &password, + ), + Err(BackendError::WalletLoginIdAlreadyExists), + )); + } + + #[test] + fn load_with_wrong_password_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = LoginId::new("first".to_string()); + + store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); + + // Trying to load it with wrong password now fails + assert!(matches!( + load_existing_login_at_file(&wallet_file, &id1, &bad_password), + Err(BackendError::DecryptionError), + )); + } + + #[test] + fn load_with_wrong_password_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = LoginId::new("first".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1, + hd_path, + id1.clone(), + &password, + ) + .unwrap(); + + // Trying to load it with wrong password now fails + assert!(matches!( + load_existing_login_at_file(&wallet_file, &id1, &bad_password), + Err(BackendError::DecryptionError), + )); + } + + #[test] + fn load_with_wrong_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); + + // Trying to load with the wrong id + assert!(matches!( + load_existing_login_at_file(&wallet_file, &id2, &password), + Err(BackendError::WalletNoSuchLoginId), + )); + } + + #[test] + fn load_with_wrong_id_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password) + .unwrap(); + + // Trying to load with the wrong id + assert!(matches!( + load_existing_login_at_file(&wallet_file, &id2, &password), + Err(BackendError::WalletNoSuchLoginId), + )); + } + + #[test] + fn store_and_load_a_single_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + + store_login_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let acc = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc.mnemonic()); + assert_eq!(&hd_path, acc.hd_path()); + } + + #[test] + fn store_and_load_a_single_login_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let acc1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + acc1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let accounts = loaded_login.as_multiple_accounts().unwrap(); + assert_eq!(accounts.len(), 1); + let account = accounts + .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) + .unwrap(); + assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); + assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.hd_path(), &hd_path); + } + + #[test] + fn store_a_second_login_with_a_different_password_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + store_login_at_file( + &wallet_file, + account1, + cosmos_hd_path.clone(), + id1, + &password, + ) + .unwrap(); + + // Can't store a second login if you use different password + assert!(matches!( + store_login_at_file(&wallet_file, account2, cosmos_hd_path, id2, &bad_password), + Err(BackendError::WalletDifferentPasswordDetected), + )); + } + + #[test] + fn store_a_second_login_with_a_different_password_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1, + hd_path.clone(), + id1, + &password, + ) + .unwrap(); + + // Can't store a second login if you use different password + assert!(matches!( + store_login_with_multiple_accounts_at_file( + &wallet_file, + account2, + hd_path, + id2, + &bad_password + ), + Err(BackendError::WalletDifferentPasswordDetected), + )); + } + + #[test] + fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + // Store the first account + store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); + + let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); + let encrypted_blob = &stored_wallet + .get_encrypted_login_by_index(0) + .unwrap() + .account; + + // keep track of salt and iv for future assertion + let original_iv = encrypted_blob.iv().to_vec(); + let original_salt = encrypted_blob.salt().to_vec(); + + // Add an extra account + store_login_at_file(&wallet_file, account2, different_hd_path, id2, &password).unwrap(); + + let loaded_accounts = load_existing_wallet_at_file(&wallet_file).unwrap(); + assert_eq!(loaded_accounts.len(), 2); + let encrypted_blob = &loaded_accounts + .get_encrypted_login_by_index(1) + .unwrap() + .account; + + // fresh IV and salt are used + assert_ne!(original_iv, encrypted_blob.iv()); + assert_ne!(original_salt, encrypted_blob.salt()); + } + + #[test] + fn store_two_mnemonic_accounts_using_two_logins() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + // Store the first account + store_login_at_file( + &wallet, + account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); + let acc = login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc.mnemonic()); + assert_eq!(&cosmos_hd_path, acc.hd_path()); + + // Add an extra account + store_login_at_file( + &wallet, + account2.clone(), + different_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // first account should be unchanged + let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + + let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); + let acc2 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account2, acc2.mnemonic()); + assert_eq!(&different_hd_path, acc2.hd_path()); + } + + #[test] + fn store_one_mnemonic_account_and_one_multi_account() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + // Store the first account + store_login_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let acc = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc.mnemonic()); + assert_eq!(&hd_path, acc.hd_path()); + + // Add an extra account + store_login_with_multiple_accounts_at_file( + &wallet_file, + account2.clone(), + different_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // first account should be unchanged + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(&hd_path, acc1.hd_path()); + + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); + let acc2 = loaded_login.as_multiple_accounts().unwrap(); + assert_eq!(acc2.len(), 1); + let account = acc2 + .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) + .unwrap(); + assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); + assert_eq!(account.mnemonic(), &account2); + assert_eq!(account.hd_path(), &different_hd_path); + } + + #[test] + fn remove_non_existent_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password) + .unwrap(); + + // Fails to delete non-existent id in the wallet + assert!(matches!( + remove_login_at_file(&wallet_file, &id2), + Err(BackendError::WalletNoSuchLoginId), + )); + } + + #[test] + fn store_and_remove_wallet_login_information() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + // Store two accounts with two different passwords + store_login_at_file( + &wallet_file, + account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + store_login_at_file( + &wallet_file, + account2.clone(), + different_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Load and compare + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); + let acc2 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account2, acc2.mnemonic()); + assert_eq!(&different_hd_path, acc2.hd_path()); + + // Delete the second account + remove_login_at_file(&wallet_file, &id2).unwrap(); + + // The first account should be unchanged + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + + // And we can't load the second one anymore + assert!(matches!( + load_existing_login_at_file(&wallet_file, &id2, &password), + Err(BackendError::WalletNoSuchLoginId), + )); + + // Delete the first account + assert!(wallet_file.exists()); + remove_login_at_file(&wallet_file, &id1).unwrap(); + + // The file should now be removed + assert!(!wallet_file.exists()); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_login_at_file(&wallet_file, &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + } + + #[test] + fn append_account_converts_the_type() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_login_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // Check that it's there as the correct non-multiple type + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let acc = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(acc.mnemonic(), &account1); + assert_eq!(acc.hd_path(), &hd_path); + + append_account_to_login_at_file( + &wallet_file, + account2.clone(), + hd_path.clone(), + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Check that it is now multiple mnemonic type + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())), + WalletAccount::new(id2, MnemonicAccount::new(account2, hd_path)), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + } + + #[test] + fn append_accounts_to_existing_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account4 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_login_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_login_at_file( + &wallet_file, + account2.clone(), + hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Check that it's there as the correct non-multiple type + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); + let acc2 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(acc2.mnemonic(), &account2); + assert_eq!(acc2.hd_path(), &hd_path); + + // Add a third and fourth mnenonic grouped together with the second one + append_account_to_login_at_file( + &wallet_file, + account3.clone(), + hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + append_account_to_login_at_file( + &wallet_file, + account4.clone(), + hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Check that we can load all four + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &account1); + assert_eq!(acc1.hd_path(), &hd_path); + + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), + WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), + WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + } + + #[test] + fn append_accounts_to_existing_login_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account4 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + account2.clone(), + hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Add a third and fourth mnenonic grouped together with the second one + append_account_to_login_at_file( + &wallet_file, + account3.clone(), + hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + append_account_to_login_at_file( + &wallet_file, + account4.clone(), + hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Check that we can load all four + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account1, hd_path.clone()), + )] + .into(); + assert_eq!(loaded_accounts, &expected); + + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account2, hd_path.clone()), + ), + WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), + WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + } + + #[test] + fn append_the_same_mnemonic_twice_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + assert!(matches!( + append_account_to_login_at_file(&wallet_file, account1, hd_path, id1, id2, &password,), + Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin), + )) + } + + #[test] + fn delete_the_same_account_twice_for_a_login_fails() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_login_at_file(&wallet, account1, hd_path.clone(), id1.clone(), &password).unwrap(); + + append_account_to_login_at_file( + &wallet, + account2, + hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); + + assert!(matches!( + remove_account_from_login_at_file(&wallet, &id1, &id2, &password), + Err(BackendError::WalletNoSuchAccountIdInWalletLogin), + )); + } + + #[test] + fn delete_the_same_account_twice_for_a_login_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet_file, + account1, + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_login_at_file( + &wallet_file, + account2, + hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_login_at_file(&wallet_file, &id1, &id2, &password).unwrap(); + + assert!(matches!( + remove_account_from_login_at_file(&wallet_file, &id1, &id2, &password), + Err(BackendError::WalletNoSuchAccountIdInWalletLogin), + )); + } + + #[test] + fn delete_appended_account_doesnt_affect_others() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + + store_login_at_file( + &wallet_file, + account1, + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_login_at_file( + &wallet_file, + account2.clone(), + hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + append_account_to_login_at_file( + &wallet_file, + account3.clone(), + hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + remove_login_at_file(&wallet_file, &id1).unwrap(); + + // The second login one is still there + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), + WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path)), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + } + + #[test] + fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet, + account1, + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_login_at_file( + &wallet, + account2, + hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_login_at_file( + &wallet, + &id1, + &DEFAULT_FIRST_ACCOUNT_NAME.into(), + &password, + ) + .unwrap(); + remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); + + // The file should now be removed + assert!(!wallet.exists()); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_login_at_file(&wallet, &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + } + + #[test] + fn remove_all_accounts_for_a_login_removes_that_login() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = LoginId::new("third".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet, + account1, + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_login_at_file( + &wallet, + account2, + hd_path.clone(), + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + store_login_with_multiple_accounts_at_file( + &wallet, + account3.clone(), + hd_path.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + remove_account_from_login_at_file( + &wallet, + &id1, + &DEFAULT_FIRST_ACCOUNT_NAME.into(), + &password, + ) + .unwrap(); + remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_login_at_file(&wallet, &id1, &password), + Err(BackendError::WalletNoSuchLoginId), + )); + + // The other login is still there + let loaded_login = load_existing_login_at_file(&wallet, &id3, &password).unwrap(); + let acc3 = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account3, hd_path), + )] + .into(); + assert_eq!(acc3, &expected); + } + + #[test] + fn append_accounts_and_remove_appended_accounts() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let acc1 = bip39::Mnemonic::generate(24).unwrap(); + let acc2 = bip39::Mnemonic::generate(24).unwrap(); + let acc3 = bip39::Mnemonic::generate(24).unwrap(); + let acc4 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_login_at_file( + &wallet, + acc1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_login_at_file( + &wallet, + acc2.clone(), + hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Add a third and fourth mnenonic grouped together with the second one + append_account_to_login_at_file( + &wallet, + acc3, + hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + append_account_to_login_at_file( + &wallet, + acc4.clone(), + hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Delete the third mnemonic, from the second login entry + remove_account_from_login_at_file(&wallet, &id2, &id3, &password).unwrap(); + + // Check that we can still load the other accounts + let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new( + id2.clone().into(), + MnemonicAccount::new(acc2, hd_path.clone()), + ), + WalletAccount::new(id4.clone(), MnemonicAccount::new(acc4, hd_path.clone())), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + + // Delete the second and fourth mnemonic from the second login entry removes the login entry + remove_account_from_login_at_file(&wallet, &id2, &id2.clone().into(), &password).unwrap(); + remove_account_from_login_at_file(&wallet, &id2, &id4, &password).unwrap(); + assert!(matches!( + load_existing_login_at_file(&wallet, &id2, &password), + Err(BackendError::WalletNoSuchLoginId), + )); + + // The first login is still available + let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); + let account = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.hd_path(), &hd_path); + } + + // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored + // wallets created with older versions. + #[test] + fn decrypt_stored_wallet() { + const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; + let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); + + let wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); + + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); + + assert!(!wallet.password_can_decrypt_all(&bad_password)); + assert!(wallet.password_can_decrypt_all(&password)); + + let acc1 = wallet.decrypt_login(&id1, &password).unwrap(); + let acc2 = wallet.decrypt_login(&id2, &password).unwrap(); + + assert!(matches!(acc1, StoredLogin::Mnemonic(_))); + assert!(matches!(acc2, StoredLogin::Mnemonic(_))); + + let expected_acc1 = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); + let expected_acc2 = bip39::Mnemonic::from_str("home mansion start quiz dress decide hint second dragon sunny juice always steak real minimum art rival skin draw total pulp foot goddess agent").unwrap(); + + assert_eq!( + acc1.as_mnemonic_account().unwrap().mnemonic(), + &expected_acc1 + ); + assert_eq!(acc1.as_mnemonic_account().unwrap().hd_path(), &hd_path,); + + assert_eq!( + acc2.as_mnemonic_account().unwrap().mnemonic(), + &expected_acc2 + ); + assert_eq!(acc2.as_mnemonic_account().unwrap().hd_path(), &hd_path,); + } + + #[test] + fn decrypt_stored_wallet_1_0_4() { + const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet-1.0.4.json"; + let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); + + let wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); + + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password11!".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let login_id = LoginId::new("default".to_string()); + + assert!(!wallet.password_can_decrypt_all(&bad_password)); + assert!(wallet.password_can_decrypt_all(&password)); + + let acc1 = wallet.decrypt_login(&login_id, &password).unwrap(); + + assert!(matches!(acc1, StoredLogin::Mnemonic(_))); + + let expected_acc1 = bip39::Mnemonic::from_str("arrow capable abstract industry elevator nominee december piece hotel feed lounge web faint sword veteran bundle hour page actual laptop horror gold test warrior").unwrap(); + + assert_eq!( + acc1.as_mnemonic_account().unwrap().mnemonic(), + &expected_acc1 + ); + assert_eq!(acc1.as_mnemonic_account().unwrap().hd_path(), &hd_path,); + } + + #[test] + fn decrypt_stored_wallet_1_0_5_with_multiple_accounts() { + const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet-1.0.5.json"; + let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); + + let wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); + + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password11!".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let login_id = LoginId::new("default".to_string()); + + assert!(!wallet.password_can_decrypt_all(&bad_password)); + assert!(wallet.password_can_decrypt_all(&password)); + + let login = wallet.decrypt_login(&login_id, &password).unwrap(); + + assert!(matches!(login, StoredLogin::Multiple(_))); + + let login = login.as_multiple_accounts().unwrap(); + assert_eq!(login.len(), 4); + + let expected_mn1 = bip39::Mnemonic::from_str("arrow capable abstract industry elevator nominee december piece hotel feed lounge web faint sword veteran bundle hour page actual laptop horror gold test warrior").unwrap(); + let expected_mn2 = bip39::Mnemonic::from_str("border hurt skull lunar goddess second danger game dismiss exhaust oven thumb dog drama onion false orchard spice tent next predict invite cherry green").unwrap(); + let expected_mn3 = bip39::Mnemonic::from_str("gentle crowd rule snap girl urge flat jump winner cluster night sand museum stock grunt quick tree acquire traffic major awake tag rack peasant").unwrap(); + let expected_mn4 = bip39::Mnemonic::from_str("debris blue skin annual inhale text border rigid spatial lesson coconut yard horn crystal control survey version vote hawk neck frame arrive oblige width").unwrap(); + + let expected = vec![ + WalletAccount::new( + "default".into(), + MnemonicAccount::new(expected_mn1, hd_path.clone()), + ), + WalletAccount::new( + "account2".into(), + MnemonicAccount::new(expected_mn2, hd_path.clone()), + ), + WalletAccount::new( + "foobar".into(), + MnemonicAccount::new(expected_mn3, hd_path.clone()), + ), + WalletAccount::new("42".into(), MnemonicAccount::new(expected_mn4, hd_path)), + ] + .into(); + + assert_eq!(login, &expected); + } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/password.rs b/nym-wallet/src-tauri/src/wallet_storage/password.rs index 8701f8994d..dfeacf33a8 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/password.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/password.rs @@ -11,33 +11,33 @@ use zeroize::Zeroize; pub(crate) struct LoginId(String); impl LoginId { - pub(crate) fn new(id: String) -> LoginId { - LoginId(id) - } + pub(crate) fn new(id: String) -> LoginId { + LoginId(id) + } } impl AsRef for LoginId { - fn as_ref(&self) -> &str { - self.0.as_ref() - } + fn as_ref(&self) -> &str { + self.0.as_ref() + } } impl From for LoginId { - fn from(id: String) -> Self { - Self::new(id) - } + fn from(id: String) -> Self { + Self::new(id) + } } impl From<&str> for LoginId { - fn from(id: &str) -> Self { - Self::new(id.to_string()) - } + fn from(id: &str) -> Self { + Self::new(id.to_string()) + } } impl fmt::Display for LoginId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } } // For each encrypted login, we can have multiple encrypted accounts. @@ -45,39 +45,39 @@ impl fmt::Display for LoginId { pub(crate) struct AccountId(String); impl AccountId { - pub(crate) fn new(id: String) -> AccountId { - AccountId(id) - } + pub(crate) fn new(id: String) -> AccountId { + AccountId(id) + } } impl AsRef for AccountId { - fn as_ref(&self) -> &str { - self.0.as_ref() - } + fn as_ref(&self) -> &str { + self.0.as_ref() + } } impl From for AccountId { - fn from(id: String) -> Self { - Self::new(id) - } + fn from(id: String) -> Self { + Self::new(id) + } } impl From<&str> for AccountId { - fn from(id: &str) -> Self { - Self::new(id.to_string()) - } + fn from(id: &str) -> Self { + Self::new(id.to_string()) + } } impl From for AccountId { - fn from(login_id: LoginId) -> Self { - Self::new(login_id.0) - } + fn from(login_id: LoginId) -> Self { + Self::new(login_id.0) + } } impl fmt::Display for AccountId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } } // simple wrapper for String that will get zeroized on drop @@ -86,17 +86,17 @@ impl fmt::Display for AccountId { pub(crate) struct UserPassword(String); impl UserPassword { - pub(crate) fn new(pass: String) -> UserPassword { - UserPassword(pass) - } + pub(crate) fn new(pass: String) -> UserPassword { + UserPassword(pass) + } - pub(crate) fn as_bytes(&self) -> &[u8] { - self.0.as_bytes() - } + pub(crate) fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } } impl AsRef for UserPassword { - fn as_ref(&self) -> &str { - self.0.as_ref() - } + fn as_ref(&self) -> &str { + self.0.as_ref() + } } From 3f718feb41609fa459eaee377556b66b73371827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 11:22:44 +0100 Subject: [PATCH 02/15] Created nymd internal coin --- .../validator-client/src/nymd/coin.rs | 44 +++++++++++++++++++ .../validator-client/src/nymd/mod.rs | 1 + 2 files changed, 45 insertions(+) create mode 100644 common/client-libs/validator-client/src/nymd/coin.rs diff --git a/common/client-libs/validator-client/src/nymd/coin.rs b/common/client-libs/validator-client/src/nymd/coin.rs new file mode 100644 index 0000000000..375af42372 --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/coin.rs @@ -0,0 +1,44 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 +use std::fmt; + +pub use cosmrs::Coin as CosmosCoin; +pub use cosmwasm_std::Coin as CosmWasmCoin; + +// the reason the coin is created here as opposed to different place in the codebase is that +// eventually we want to either publish the cosmwasm client separately or commit it to +// some other project, like cosmrs. Either way, in that case we can't really have +// a dependency on an internal type +pub struct Coin { + pub amount: u128, + pub denom: String, +} + +impl fmt::Display for Coin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", self.amount, self.denom) + } +} + +impl From for CosmosCoin { + fn from(coin: Coin) -> Self { + assert!( + coin.amount <= u64::MAX as u128, + "the coin amount is higher than the maximum supported by cosmrs" + ); + + CosmosCoin { + denom: coin + .denom + .parse() + .expect("the coin should have had a valid denom!"), + amount: (coin.amount as u64).into(), + } + } +} + +impl From for CosmWasmCoin { + fn from(coin: Coin) -> Self { + CosmWasmCoin::new(coin.amount, coin.denom) + } +} diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 03adeb8e5b..cef740c993 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -46,6 +46,7 @@ pub use cosmrs::{bip32, AccountId, Decimal, Denom}; pub use signing_client::Client as SigningNymdClient; pub use traits::{VestingQueryClient, VestingSigningClient}; +pub mod coin; pub mod cosmwasm_client; pub mod error; pub mod fee; From 972c60726c9695c08d73fb28b57142386c8a7862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 11:31:58 +0100 Subject: [PATCH 03/15] Additional From implementations plus a constructor --- .../validator-client/src/nymd/coin.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/common/client-libs/validator-client/src/nymd/coin.rs b/common/client-libs/validator-client/src/nymd/coin.rs index 375af42372..821059791c 100644 --- a/common/client-libs/validator-client/src/nymd/coin.rs +++ b/common/client-libs/validator-client/src/nymd/coin.rs @@ -14,6 +14,12 @@ pub struct Coin { pub denom: String, } +impl Coin { + pub fn new(amount: u128, denom: String) -> Self { + Coin { amount, denom } + } +} + impl fmt::Display for Coin { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", self.amount, self.denom) @@ -37,8 +43,30 @@ impl From for CosmosCoin { } } +impl From for Coin { + fn from(coin: CosmosCoin) -> Self { + Coin { + amount: coin + .amount + .to_string() + .parse() + .expect("somehow failed to parse string representation of u64"), + denom: coin.denom.to_string(), + } + } +} + impl From for CosmWasmCoin { fn from(coin: Coin) -> Self { CosmWasmCoin::new(coin.amount, coin.denom) } } + +impl From for Coin { + fn from(coin: CosmWasmCoin) -> Self { + Coin { + amount: coin.amount.u128(), + denom: coin.denom, + } + } +} From 5925e8568437a38434e6440100896b8d3961e89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 11:37:18 +0100 Subject: [PATCH 04/15] try_add --- .../validator-client/src/nymd/coin.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/common/client-libs/validator-client/src/nymd/coin.rs b/common/client-libs/validator-client/src/nymd/coin.rs index 821059791c..a82dcc8da0 100644 --- a/common/client-libs/validator-client/src/nymd/coin.rs +++ b/common/client-libs/validator-client/src/nymd/coin.rs @@ -1,14 +1,19 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use serde::{Deserialize, Serialize}; use std::fmt; pub use cosmrs::Coin as CosmosCoin; pub use cosmwasm_std::Coin as CosmWasmCoin; +#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq)] +pub struct MismatchedDenoms; + // the reason the coin is created here as opposed to different place in the codebase is that // eventually we want to either publish the cosmwasm client separately or commit it to // some other project, like cosmrs. Either way, in that case we can't really have // a dependency on an internal type +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)] pub struct Coin { pub amount: u128, pub denom: String, @@ -18,6 +23,17 @@ impl Coin { pub fn new(amount: u128, denom: String) -> Self { Coin { amount, denom } } + + pub fn try_add(&self, other: &Self) -> Result { + if self.denom != other.denom { + Err(MismatchedDenoms) + } else { + Ok(Coin { + amount: self.amount + other.amount, + denom: self.denom.clone(), + }) + } + } } impl fmt::Display for Coin { From 84a42f6ed9b63fc662f4707e9ebd5ee843bbfc22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 14:46:05 +0100 Subject: [PATCH 05/15] Changed client API to use the new coin type --- .../src/nymd/cosmwasm_client/client.rs | 21 +-- .../nymd/cosmwasm_client/signing_client.rs | 82 ++++++--- .../src/nymd/cosmwasm_client/types.rs | 21 ++- .../src/nymd/fee/gas_price.rs | 14 +- .../validator-client/src/nymd/mod.rs | 87 ++++------ .../src/nymd/traits/vesting_query_client.rs | 21 ++- .../src/nymd/traits/vesting_signing_client.rs | 163 ++++++++++-------- 7 files changed, 223 insertions(+), 186 deletions(-) diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs index 594092bc86..08a3ce22a2 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::nymd::coin::Coin; use crate::nymd::cosmwasm_client::helpers::{create_pagination, next_page_key}; use crate::nymd::cosmwasm_client::types::{ Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId, @@ -25,8 +26,7 @@ use cosmrs::rpc::{self, HttpClient, Order}; use cosmrs::tendermint::abci::Code as AbciCode; use cosmrs::tendermint::abci::Transaction; use cosmrs::tendermint::{abci, block, chain}; -use cosmrs::{tx, AccountId, Coin, Denom, Tx}; -use cosmwasm_std::Coin as CosmWasmCoin; +use cosmrs::{tx, AccountId, Coin as CosmosCoin, Denom, Tx}; use prost::Message; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; @@ -135,7 +135,7 @@ pub trait CosmWasmClient: rpc::Client { .await?; res.balance - .map(TryFrom::try_from) + .map(|proto| CosmosCoin::try_from(proto).map(Into::into)) .transpose() .map_err(|_| NymdError::SerializationError("Coin".to_owned())) } @@ -166,16 +166,12 @@ pub trait CosmWasmClient: rpc::Client { raw_balances .into_iter() - .map(TryFrom::try_from) + .map(|proto| CosmosCoin::try_from(proto).map(Into::into)) .collect::>() .map_err(|_| NymdError::SerializationError("Coins".to_owned())) } - // this is annoyingly and inconsistently returning `Vec` rather than - // Vec, since cosmrs::Coin can't deal with IBC denoms. - // Presumably after https://github.com/cosmos/cosmos-rust/issues/173 is resolved, - // the code could be adjusted - async fn get_total_supply(&self) -> Result, NymdError> { + async fn get_total_supply(&self) -> Result, NymdError> { let path = Some("/cosmos.bank.v1beta1.Query/TotalSupply".parse().unwrap()); let mut supply = Vec::new(); @@ -198,12 +194,7 @@ pub trait CosmWasmClient: rpc::Client { supply .into_iter() - .map(|coin| { - coin.amount.parse().map(|amount| CosmWasmCoin { - denom: coin.denom, - amount, - }) - }) + .map(|proto| CosmosCoin::try_from(proto).map(Into::into)) .collect::>() .map_err(|_| NymdError::SerializationError("Coins".to_owned())) } diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index 58e10c8655..13a3ac6678 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -17,7 +17,7 @@ use cosmrs::rpc::endpoint::broadcast; use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest}; use cosmrs::staking::{MsgDelegate, MsgUndelegate}; use cosmrs::tx::{self, Msg, SignDoc, SignerInfo}; -use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin, Tx}; +use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin as CosmosCoin, Tx}; use log::debug; use serde::Serialize; use sha2::Digest; @@ -294,23 +294,45 @@ pub trait SigningCosmWasmClient: CosmWasmClient { }) } - async fn execute( + // a helper to automatically infer required types so that you wouldn't need the turbofish + // for normal `execute` if you're not sending any funds with your transaction + async fn fundless_execute( &self, sender_address: &AccountId, contract_address: &AccountId, msg: &M, fee: Fee, memo: impl Into + Send + 'static, - funds: Vec, ) -> Result where M: ?Sized + Serialize + Sync, + { + // todo!() + self.execute::<_, CosmosCoin, _>(sender_address, contract_address, msg, fee, memo, vec![]) + .await + } + + // the memo had to be extracted into an explicit generic due to: https://github.com/rust-lang/rust/issues/83701 + async fn execute( + &self, + sender_address: &AccountId, + contract_address: &AccountId, + msg: &M, + fee: Fee, + memo: S, + funds: Vec, + ) -> Result + where + M: ?Sized + Serialize + Sync, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, + S: Into + Send + 'static, { let execute_msg = cosmwasm::MsgExecuteContract { sender: sender_address.clone(), contract: contract_address.clone(), msg: serde_json::to_vec(msg)?, - funds, + funds: funds.into_iter().map(Into::into).collect(), } .to_any() .map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?; @@ -330,7 +352,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { }) } - async fn execute_multiple( + async fn execute_multiple( &self, sender_address: &AccountId, contract_address: &AccountId, @@ -339,7 +361,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient { memo: impl Into + Send + 'static, ) -> Result where - I: IntoIterator)> + Send, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, + I: IntoIterator)> + Send, M: Serialize, { let messages = msgs @@ -349,7 +373,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { sender: sender_address.clone(), contract: contract_address.clone(), msg: serde_json::to_vec(&msg)?, - funds, + funds: funds.into_iter().map(Into::into).collect(), } .to_any() .map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned())) @@ -371,18 +395,22 @@ pub trait SigningCosmWasmClient: CosmWasmClient { }) } - async fn send_tokens( + async fn send_tokens( &self, sender_address: &AccountId, recipient_address: &AccountId, - amount: Vec, + amount: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result + where + // this allows you to use both CosmosCoin and Coin + T: Into + Send, + { let send_msg = MsgSend { from_address: sender_address.clone(), to_address: recipient_address.clone(), - amount, + amount: amount.into_iter().map(Into::into).collect(), } .to_any() .map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?; @@ -392,7 +420,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { .check_response() } - async fn send_tokens_multiple( + async fn send_tokens_multiple( &self, sender_address: &AccountId, msgs: I, @@ -400,7 +428,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient { memo: impl Into + Send + 'static, ) -> Result where - I: IntoIterator)> + Send, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, + I: IntoIterator)> + Send, { let messages = msgs .into_iter() @@ -408,7 +438,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { MsgSend { from_address: sender_address.clone(), to_address, - amount, + amount: amount.into_iter().map(Into::into).collect(), } .to_any() .map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned())) @@ -420,18 +450,22 @@ pub trait SigningCosmWasmClient: CosmWasmClient { .check_response() } - async fn delegate_tokens( + async fn delegate_tokens( &self, delegator_address: &AccountId, validator_address: &AccountId, - amount: Coin, + amount: T, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result + where + // this allows you to use both CosmosCoin and Coin + T: Into + Send, + { let delegate_msg = MsgDelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), - amount, + amount: amount.into(), } .to_any() .map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?; @@ -441,18 +475,22 @@ pub trait SigningCosmWasmClient: CosmWasmClient { .check_response() } - async fn undelegate_tokens( + async fn undelegate_tokens( &self, delegator_address: &AccountId, validator_address: &AccountId, - amount: Coin, + amount: T, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result + where + // this allows you to use both CosmosCoin and Coin + T: Into + Send, + { let undelegate_msg = MsgUndelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), - amount, + amount: amount.into(), } .to_any() .map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs index 5d3994bd15..3c28d8991f 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs @@ -28,7 +28,7 @@ use cosmrs::proto::cosmwasm::wasm::v1::{ use cosmrs::tendermint::abci::Data; use cosmrs::tendermint::{abci, chain}; use cosmrs::tx::{AccountNumber, Gas, SequenceNumber}; -use cosmrs::{tx, AccountId, Any, Coin}; +use cosmrs::{tx, AccountId, Any, Coin as CosmosCoin}; use prost::Message; use serde::Serialize; use std::convert::{TryFrom, TryInto}; @@ -107,9 +107,9 @@ impl TryFrom for ModuleAccount { #[derive(Debug)] pub struct BaseVestingAccount { pub base_account: Option, - pub original_vesting: Vec, - pub delegated_free: Vec, - pub delegated_vesting: Vec, + pub original_vesting: Vec, + pub delegated_free: Vec, + pub delegated_vesting: Vec, pub end_time: i64, } @@ -184,7 +184,7 @@ impl TryFrom for DelayedVestingAccount { #[derive(Debug)] pub struct Period { pub length: i64, - pub amount: Vec, + pub amount: Vec, } impl TryFrom for Period { @@ -628,7 +628,7 @@ pub struct InstantiateOptions { /// created and before the instantiation message is executed by the contract. /// /// Only native tokens are supported. - pub funds: Vec, + pub funds: Vec, /// A bech32 encoded address of an admin account. /// Caution: an admin has the privilege to upgrade a contract. @@ -636,6 +636,15 @@ pub struct InstantiateOptions { pub admin: Option, } +impl InstantiateOptions { + pub fn new>(funds: Vec, admin: Option) -> Self { + InstantiateOptions { + funds: funds.into_iter().map(Into::into).collect(), + admin, + } + } +} + #[derive(Debug)] pub struct InstantiateResult { /// The address of the newly instantiated contract diff --git a/common/client-libs/validator-client/src/nymd/fee/gas_price.rs b/common/client-libs/validator-client/src/nymd/fee/gas_price.rs index e7e69a32a5..9d26f1bd49 100644 --- a/common/client-libs/validator-client/src/nymd/fee/gas_price.rs +++ b/common/client-libs/validator-client/src/nymd/fee/gas_price.rs @@ -4,7 +4,7 @@ use crate::nymd::error::NymdError; use config::defaults; use cosmrs::tx::Gas; -use cosmrs::{Coin, Denom}; +use cosmrs::Coin; use cosmwasm_std::{Decimal, Fraction, Uint128}; use std::ops::Mul; use std::str::FromStr; @@ -13,11 +13,12 @@ use std::str::FromStr; /// the smallest fee token unit, such as 0.012utoken. #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct GasPrice { - // I really hate the combination of cosmwasm Decimal with cosmos-sdk Denom, - // but cosmos-sdk's Decimal is too basic for our needs + // I really dislike the usage of cosmwasm Decimal, but I didn't feel like implementing + // our own maths subcrate just for the purposes of calculating gas requirements + // this should definitely be rectified later on pub amount: Decimal, - pub denom: Denom, + pub denom: String, } impl<'a> Mul for &'a GasPrice { @@ -44,7 +45,10 @@ impl<'a> Mul for &'a GasPrice { assert!(amount.u128() <= u64::MAX as u128); Coin { - denom: self.denom.clone(), + denom: self + .denom + .parse() + .expect("the gas price has been created with invalid denom"), amount: (amount.u128() as u64).into(), } } diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index cef740c993..8769433dc1 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -9,10 +9,11 @@ use crate::nymd::cosmwasm_client::types::{ use crate::nymd::error::NymdError; use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER; use crate::nymd::wallet::DirectSecp256k1HdWallet; +use cosmrs::cosmwasm; use cosmrs::rpc::Error as TendermintRpcError; use cosmrs::rpc::HttpClientUrl; use cosmrs::tx::Msg; -use cosmwasm_std::{Coin, Uint128}; +use cosmwasm_std::Uint128; pub use fee::gas_price::GasPrice; use mixnet_contract_common::mixnode::DelegationEvent; use mixnet_contract_common::{ @@ -28,8 +29,8 @@ use std::convert::TryInto; pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; pub use crate::nymd::fee::Fee; +pub use coin::Coin; pub use cosmrs::bank::MsgSend; -use cosmrs::cosmwasm; pub use cosmrs::rpc::endpoint::tx::Response as TxResponse; pub use cosmrs::rpc::endpoint::validators::Response as ValidatorResponse; pub use cosmrs::rpc::HttpClient as QueryNymdClient; @@ -266,7 +267,7 @@ impl NymdClient { &self, address: &AccountId, denom: Denom, - ) -> Result, NymdError> + ) -> Result, NymdError> where C: CosmWasmClient + Sync, { @@ -811,7 +812,7 @@ impl NymdClient { &req, fee, "Bonding mixnode from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -842,7 +843,7 @@ impl NymdClient { &req, fee, "Bonding mixnode on behalf from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -858,7 +859,7 @@ impl NymdClient { { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); - let reqs: Vec<(ExecuteMsg, Vec)> = mixnode_bonds_with_sigs + let reqs: Vec<(ExecuteMsg, Vec)> = mixnode_bonds_with_sigs .into_iter() .map(|(bond, owner_signature)| { ( @@ -867,7 +868,7 @@ impl NymdClient { owner: bond.owner.to_string(), owner_signature, }, - vec![cosmwasm_coin_to_cosmos_coin(bond.pledge_amount)], + vec![bond.pledge_amount.into()], ) }) .collect(); @@ -892,13 +893,12 @@ impl NymdClient { let req = ExecuteMsg::UnbondMixnode {}; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding mixnode from rust!", - Vec::new(), ) .await } @@ -916,13 +916,12 @@ impl NymdClient { let req = ExecuteMsg::UnbondMixnodeOnBehalf { owner }; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding mixnode on behalf from rust!", - Vec::new(), ) .await } @@ -942,13 +941,12 @@ impl NymdClient { profit_margin_percent, }; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Updating mixnode configuration from rust!", - Vec::new(), ) .await } @@ -957,7 +955,7 @@ impl NymdClient { pub async fn delegate_to_mixnode( &self, mix_identity: &str, - amount: &Coin, + amount: Coin, fee: Option, ) -> Result where @@ -975,7 +973,7 @@ impl NymdClient { &req, fee, "Delegating to mixnode from rust!", - vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)], + vec![amount], ) .await } @@ -986,7 +984,7 @@ impl NymdClient { &self, mix_identity: &str, delegate: &str, - amount: &Coin, + amount: Coin, fee: Option, ) -> Result where @@ -1005,7 +1003,7 @@ impl NymdClient { &req, fee, "Delegating to mixnode on behalf from rust!", - vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)], + vec![amount], ) .await } @@ -1021,7 +1019,7 @@ impl NymdClient { { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); - let reqs: Vec<(ExecuteMsg, Vec)> = mixnode_delegations + let reqs: Vec<(ExecuteMsg, Vec)> = mixnode_delegations .into_iter() .map(|delegation| { ( @@ -1029,7 +1027,7 @@ impl NymdClient { mix_identity: delegation.node_identity(), delegate: delegation.owner().to_string(), }, - vec![cosmwasm_coin_to_cosmos_coin(delegation.amount().clone())], + vec![delegation.amount().clone().into()], ) }) .collect(); @@ -1060,13 +1058,12 @@ impl NymdClient { mix_identity: mix_identity.to_string(), }; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Removing mixnode delegation from rust!", - Vec::new(), ) .await } @@ -1088,13 +1085,12 @@ impl NymdClient { delegate: delegate.to_string(), }; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Removing mixnode delegation on behalf from rust!", - Vec::new(), ) .await } @@ -1123,7 +1119,7 @@ impl NymdClient { &req, fee, "Bonding gateway from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -1154,7 +1150,7 @@ impl NymdClient { &req, fee, "Bonding gateway on behalf from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -1170,7 +1166,7 @@ impl NymdClient { { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); - let reqs: Vec<(ExecuteMsg, Vec)> = gateway_bonds_with_sigs + let reqs: Vec<(ExecuteMsg, Vec)> = gateway_bonds_with_sigs .into_iter() .map(|(bond, owner_signature)| { ( @@ -1179,7 +1175,7 @@ impl NymdClient { owner: bond.owner.to_string(), owner_signature, }, - vec![cosmwasm_coin_to_cosmos_coin(bond.pledge_amount)], + vec![bond.pledge_amount.into()], ) }) .collect(); @@ -1204,13 +1200,12 @@ impl NymdClient { let req = ExecuteMsg::UnbondGateway {}; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding gateway from rust!", - Vec::new(), ) .await } @@ -1229,13 +1224,12 @@ impl NymdClient { let req = ExecuteMsg::UnbondGatewayOnBehalf { owner }; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding gateway on behalf from rust!", - Vec::new(), ) .await } @@ -1252,13 +1246,12 @@ impl NymdClient { let req = ExecuteMsg::UpdateContractStateParams(new_params); self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Updating contract state from rust!", - Vec::new(), ) .await } @@ -1271,13 +1264,12 @@ impl NymdClient { let req = ExecuteMsg::AdvanceCurrentEpoch {}; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Advance current epoch", - Vec::new(), ) .await } @@ -1290,13 +1282,12 @@ impl NymdClient { let req = ExecuteMsg::ReconcileDelegations {}; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Reconciling delegation events", - Vec::new(), ) .await } @@ -1309,13 +1300,12 @@ impl NymdClient { let req = ExecuteMsg::CheckpointMixnodes {}; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Snapshotting mixnodes", - Vec::new(), ) .await } @@ -1336,30 +1326,13 @@ impl NymdClient { expected_active_set_size, }; self.client - .execute( + .fundless_execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Writing rewarded set", - Vec::new(), ) .await } } - -fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin { - CosmosCoin { - denom: coin.denom.parse().unwrap(), - // this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64 - amount: (coin.amount.u128() as u64).into(), - } -} - -fn cosmwasm_coin_ptr_to_cosmos_coin(coin: &Coin) -> CosmosCoin { - CosmosCoin { - denom: coin.denom.parse().unwrap(), - // this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64 - amount: (coin.amount.u128() as u64).into(), - } -} diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs index 3101850b7c..a020dea2ea 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs @@ -1,11 +1,12 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::nymd::coin::Coin; pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; use crate::nymd::error::NymdError; use crate::nymd::NymdClient; use async_trait::async_trait; -use cosmwasm_std::{Coin, Timestamp}; +use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp}; use vesting_contract::vesting::Account; use vesting_contract_common::{ messages::QueryMsg as VestingQueryMsg, OriginalVestingResponse, Period, PledgeData, @@ -83,8 +84,9 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) .await + .map(Into::into) } async fn spendable_coins( @@ -97,8 +99,9 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) .await + .map(Into::into) } async fn vested_coins( &self, @@ -110,8 +113,9 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) .await + .map(Into::into) } async fn vesting_coins( &self, @@ -123,8 +127,9 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) .await + .map(Into::into) } async fn vesting_start_time( @@ -173,8 +178,9 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) .await + .map(Into::into) } async fn delegated_vesting( @@ -187,8 +193,9 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) .await + .map(Into::into) } async fn get_account(&self, address: &str) -> Result { diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs index 073cfb7052..b38b53b408 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs @@ -4,9 +4,10 @@ pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; use crate::nymd::cosmwasm_client::types::ExecuteResult; use crate::nymd::error::NymdError; -use crate::nymd::{cosmwasm_coin_to_cosmos_coin, Fee, NymdClient}; +use crate::nymd::{Fee, NymdClient}; use async_trait::async_trait; -use cosmwasm_std::Coin; +use cosmrs::Coin as CosmosCoin; +use cosmwasm_std::Coin as CosmWasmCoin; use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode}; use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification}; @@ -24,57 +25,57 @@ pub trait VestingSigningClient { fee: Option, ) -> Result; - async fn vesting_bond_gateway( + async fn vesting_bond_gateway + Send>( &self, gateway: Gateway, owner_signature: &str, - pledge: Coin, + pledge: T, fee: Option, ) -> Result; async fn vesting_unbond_gateway(&self, fee: Option) -> Result; - async fn vesting_track_unbond_gateway( + async fn vesting_track_unbond_gateway + Send>( &self, owner: &str, - amount: Coin, + amount: T, fee: Option, ) -> Result; - async fn vesting_bond_mixnode( + async fn vesting_bond_mixnode + Send>( &self, mix_node: MixNode, owner_signature: &str, - pledge: Coin, + pledge: T, fee: Option, ) -> Result; async fn vesting_unbond_mixnode(&self, fee: Option) -> Result; - async fn vesting_track_unbond_mixnode( + async fn vesting_track_unbond_mixnode + Send>( &self, owner: &str, - amount: Coin, + amount: T, fee: Option, ) -> Result; - async fn withdraw_vested_coins( + async fn withdraw_vested_coins + Send>( &self, - amount: Coin, + amount: T, fee: Option, ) -> Result; - async fn vesting_track_undelegation( + async fn vesting_track_undelegation + Send>( &self, address: &str, mix_identity: IdentityKey, - amount: Coin, + amount: T, fee: Option, ) -> Result; - async fn vesting_delegate_to_mixnode<'a>( + async fn vesting_delegate_to_mixnode<'a, T: Into + Send>( &self, mix_identity: IdentityKeyRef<'a>, - amount: &Coin, + amount: T, fee: Option, ) -> Result; @@ -84,12 +85,12 @@ pub trait VestingSigningClient { fee: Option, ) -> Result; - async fn create_periodic_vesting_account( + async fn create_periodic_vesting_account + Send>( &self, owner_address: &str, staking_address: Option, vesting_spec: Option, - amount: Coin, + amount: T, fee: Option, ) -> Result; } @@ -106,13 +107,12 @@ impl VestingSigningClient for NymdClient profit_margin_percent, }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UpdateMixnetConfig", - vec![], ) .await } @@ -127,38 +127,39 @@ impl VestingSigningClient for NymdClient address: address.to_string(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UpdateMixnetAddress", - vec![], ) .await } - async fn vesting_bond_gateway( + async fn vesting_bond_gateway( &self, gateway: Gateway, owner_signature: &str, - pledge: Coin, + pledge: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::BondGateway { gateway, owner_signature: owner_signature.to_string(), - amount: pledge, + amount: pledge.into(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::BondGateway", - vec![], ) .await } @@ -167,61 +168,64 @@ impl VestingSigningClient for NymdClient let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UnbondGateway {}; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UnbondGateway", - vec![], ) .await } - async fn vesting_track_unbond_gateway( + async fn vesting_track_unbond_gateway( &self, owner: &str, - amount: Coin, + amount: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUnbondGateway { owner: owner.to_string(), - amount, + amount: amount.into(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::TrackUnbondGateway", - vec![], ) .await } - async fn vesting_bond_mixnode( + async fn vesting_bond_mixnode( &self, mix_node: MixNode, owner_signature: &str, - pledge: Coin, + pledge: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::BondMixnode { mix_node, owner_signature: owner_signature.to_string(), - amount: pledge, + amount: pledge.into(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::BondMixnode", - vec![], ) .await } @@ -230,100 +234,109 @@ impl VestingSigningClient for NymdClient let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UnbondMixnode {}; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UnbondMixnode", - vec![], ) .await } - async fn vesting_track_unbond_mixnode( + async fn vesting_track_unbond_mixnode( &self, owner: &str, - amount: Coin, + amount: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUnbondMixnode { owner: owner.to_string(), - amount, + amount: amount.into(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::TrackUnbondMixnode", - vec![], ) .await } - async fn withdraw_vested_coins( + async fn withdraw_vested_coins( &self, - amount: Coin, + amount: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); - let req = VestingExecuteMsg::WithdrawVestedCoins { amount }; + let req = VestingExecuteMsg::WithdrawVestedCoins { + amount: amount.into(), + }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::WithdrawVested", - vec![], ) .await } - async fn vesting_track_undelegation( + async fn vesting_track_undelegation( &self, address: &str, mix_identity: IdentityKey, - amount: Coin, + amount: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUndelegation { owner: address.to_string(), mix_identity, - amount, + amount: amount.into(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::TrackUndelegation", - vec![], ) .await } - async fn vesting_delegate_to_mixnode<'a>( + async fn vesting_delegate_to_mixnode<'a, T>( &self, mix_identity: IdentityKeyRef<'a>, - amount: &Coin, + amount: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::DelegateToMixnode { mix_identity: mix_identity.into(), - amount: amount.clone(), + amount: amount.into(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::DelegateToMixnode", - vec![], ) .await } @@ -338,25 +351,27 @@ impl VestingSigningClient for NymdClient mix_identity: mix_identity.into(), }; self.client - .execute( + .fundless_execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UndelegateFromMixnode", - vec![], ) .await } - async fn create_periodic_vesting_account( + async fn create_periodic_vesting_account( &self, owner_address: &str, staking_address: Option, vesting_spec: Option, - amount: Coin, + amount: T, fee: Option, - ) -> Result { + ) -> Result + where + T: Into + Send, + { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::CreateAccount { owner_address: owner_address.to_string(), @@ -370,7 +385,7 @@ impl VestingSigningClient for NymdClient &req, fee, "VestingContract::CreatePeriodicVestingAccount", - vec![cosmwasm_coin_to_cosmos_coin(amount)], + vec![amount.into()], ) .await } From ecca7e8479397727a9e43b82629ad7a273de18cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 14:51:12 +0100 Subject: [PATCH 06/15] CoinConverter trait --- .../validator-client/src/nymd/coin.rs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/common/client-libs/validator-client/src/nymd/coin.rs b/common/client-libs/validator-client/src/nymd/coin.rs index a82dcc8da0..92789f63a5 100644 --- a/common/client-libs/validator-client/src/nymd/coin.rs +++ b/common/client-libs/validator-client/src/nymd/coin.rs @@ -86,3 +86,42 @@ impl From for Coin { } } } + +pub trait CoinConverter { + type Target; + + fn convert_coin(&self) -> Self::Target; +} + +impl CoinConverter for CosmosCoin { + type Target = CosmWasmCoin; + + fn convert_coin(&self) -> Self::Target { + CosmWasmCoin::new( + self.amount + .to_string() + .parse() + .expect("cosmos coin had an invalid amount assigned"), + self.denom.to_string(), + ) + } +} + +impl CoinConverter for CosmWasmCoin { + type Target = CosmosCoin; + + fn convert_coin(&self) -> Self::Target { + assert!( + self.amount.u128() <= u64::MAX as u128, + "the coin amount is higher than the maximum supported by cosmrs" + ); + + CosmosCoin { + denom: self + .denom + .parse() + .expect("cosmwasm coin had an invalid amount assigned"), + amount: (self.amount.u128() as u64).into(), + } + } +} From 766c31544e5fb12047cebbd1368b82164ac6622a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 15:58:31 +0100 Subject: [PATCH 07/15] Made wallet compilable with the recent changes --- .../validator-client/src/nymd/coin.rs | 7 +- .../nymd/cosmwasm_client/signing_client.rs | 1 - .../validator-client/src/nymd/mod.rs | 43 ++++++--- nym-wallet/src-tauri/src/coin.rs | 89 +++++++++++-------- nym-wallet/src-tauri/src/error.rs | 3 + nym-wallet/src-tauri/src/network.rs | 17 ++-- .../src/operations/mixnet/account.rs | 12 +-- .../src-tauri/src/operations/mixnet/bond.rs | 6 +- .../src/operations/mixnet/delegate.rs | 7 +- .../src-tauri/src/operations/mixnet/send.rs | 9 +- .../src/operations/simulate/admin.rs | 3 +- .../src/operations/simulate/cosmos.rs | 5 +- .../src/operations/simulate/mixnet.rs | 31 +++---- .../src/operations/simulate/vesting.rs | 45 ++++------ .../src-tauri/src/operations/vesting/bond.rs | 9 +- .../src/operations/vesting/delegate.rs | 5 +- 16 files changed, 150 insertions(+), 142 deletions(-) diff --git a/common/client-libs/validator-client/src/nymd/coin.rs b/common/client-libs/validator-client/src/nymd/coin.rs index 92789f63a5..77451032ac 100644 --- a/common/client-libs/validator-client/src/nymd/coin.rs +++ b/common/client-libs/validator-client/src/nymd/coin.rs @@ -20,8 +20,11 @@ pub struct Coin { } impl Coin { - pub fn new(amount: u128, denom: String) -> Self { - Coin { amount, denom } + pub fn new>(amount: u128, denom: S) -> Self { + Coin { + amount, + denom: denom.into(), + } } pub fn try_add(&self, other: &Self) -> Result { diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index 13a3ac6678..da8a4ff6ad 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -307,7 +307,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient { where M: ?Sized + Serialize + Sync, { - // todo!() self.execute::<_, CosmosCoin, _>(sender_address, contract_address, msg, fee, memo, vec![]) .await } diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 8769433dc1..c1bf428e40 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -44,6 +44,7 @@ pub use cosmrs::tendermint::Time as TendermintTime; pub use cosmrs::tx::{self, Gas}; pub use cosmrs::Coin as CosmosCoin; pub use cosmrs::{bip32, AccountId, Decimal, Denom}; +pub use cosmwasm_std::Coin as CosmWasmCoin; pub use signing_client::Client as SigningNymdClient; pub use traits::{VestingQueryClient, VestingSigningClient}; @@ -173,21 +174,35 @@ impl NymdClient { self.simulated_gas_multiplier = multiplier; } - pub fn wrap_contract_execute_message( + pub fn wrap_fundless_contract_execute_message( &self, contract_address: &AccountId, msg: &M, - funds: Vec, ) -> Result where C: SigningCosmWasmClient, M: ?Sized + Serialize, + { + self.wrap_contract_execute_message::<_, CosmosCoin>(contract_address, msg, vec![]) + } + + pub fn wrap_contract_execute_message( + &self, + contract_address: &AccountId, + msg: &M, + funds: Vec, + ) -> Result + where + C: SigningCosmWasmClient, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, + M: ?Sized + Serialize, { Ok(cosmwasm::MsgExecuteContract { sender: self.address().clone(), contract: contract_address.clone(), msg: serde_json::to_vec(msg)?, - funds, + funds: funds.into_iter().map(Into::into).collect(), }) } @@ -639,15 +654,17 @@ impl NymdClient { } /// Send funds from one address to another - pub async fn send( + pub async fn send( &self, recipient: &AccountId, - amount: Vec, + amount: Vec, memo: impl Into + Send + 'static, fee: Option, ) -> Result where C: SigningCosmWasmClient + Sync, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); self.client @@ -656,14 +673,16 @@ impl NymdClient { } /// Send funds from one address to multiple others - pub async fn send_multiple( + pub async fn send_multiple( &self, - msgs: Vec<(AccountId, Vec)>, + msgs: Vec<(AccountId, Vec)>, memo: impl Into + Send + 'static, fee: Option, ) -> Result where C: SigningCosmWasmClient + Sync, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); self.client @@ -671,16 +690,18 @@ impl NymdClient { .await } - pub async fn execute( + pub async fn execute( &self, contract_address: &AccountId, msg: &M, fee: Fee, memo: impl Into + Send + 'static, - funds: Vec, + funds: Vec, ) -> Result where C: SigningCosmWasmClient + Sync, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, M: ?Sized + Serialize + Sync, { self.client @@ -688,7 +709,7 @@ impl NymdClient { .await } - pub async fn execute_multiple( + pub async fn execute_multiple( &self, contract_address: &AccountId, msgs: I, @@ -697,6 +718,8 @@ impl NymdClient { ) -> Result where C: SigningCosmWasmClient + Sync, + // this allows you to use both CosmosCoin and Coin + T: Into + Send, I: IntoIterator)> + Send, M: Serialize, { diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index 3e2078c069..146ca21532 100644 --- a/nym-wallet/src-tauri/src/coin.rs +++ b/nym-wallet/src-tauri/src/coin.rs @@ -2,16 +2,16 @@ use crate::error::BackendError; use crate::network::Network; -use cosmwasm_std::Coin as CosmWasmCoin; -use cosmwasm_std::Uint128; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::ops::{Add, Sub}; use std::str::FromStr; use strum::IntoEnumIterator; use validator_client::nymd::CosmosCoin; -use validator_client::nymd::Decimal; use validator_client::nymd::Denom as CosmosDenom; +use validator_client::nymd::{Coin as BackendCoin, CosmWasmCoin}; + +const MINOR_IN_MAJOR: f64 = 1_000_000.; #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export, export_to = "../src/types/rust/denom.ts"))] @@ -21,8 +21,6 @@ pub enum Denom { Minor, } -const MINOR_IN_MAJOR: f64 = 1_000_000.; - impl FromStr for Denom { type Err = BackendError; @@ -30,9 +28,9 @@ impl FromStr for Denom { let s = s.to_lowercase(); for network in Network::iter() { let denom = network.denom(); - if s == denom.as_ref().to_lowercase() || s == "minor" { + if s == denom.to_lowercase() || s == "minor" { return Ok(Denom::Minor); - } else if s == denom.as_ref()[1..].to_lowercase() || s == "major" { + } else if s == denom[1..].to_lowercase() || s == "major" { return Ok(Denom::Major); } } @@ -64,8 +62,8 @@ impl Add for Coin { 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 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(), @@ -86,8 +84,8 @@ impl Sub for Coin { 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 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(), @@ -154,38 +152,51 @@ impl Coin { } // Helper function that returns the local denom in terms of the specified network denom. - fn denom_as_string(&self, network_denom: &CosmosDenom) -> Result { - // Currently there is the widespread assumption that network denomination is always in - // `Denom::Minor`, and starts with 'u'. - let network_denom = network_denom.to_string(); - if !network_denom.starts_with('u') { - return Err(BackendError::InvalidNetworkDenom(network_denom)); - } + // fn denom_as_string(&self, network_denom: &CosmosDenom) -> Result { + // // Currently there is the widespread assumption that network denomination is always in + // // `Denom::Minor`, and starts with 'u'. + // let network_denom = network_denom.to_string(); + // if !network_denom.starts_with('u') { + // return Err(BackendError::InvalidNetworkDenom(network_denom)); + // } + // + // Ok(match &self.denom { + // Denom::Minor => network_denom, + // Denom::Major => network_denom[1..].to_string(), + // }) + // } - Ok(match &self.denom { - Denom::Minor => network_denom, - Denom::Major => network_denom[1..].to_string(), - }) + pub fn into_backend_coin(self, network_denom: &str) -> Result { + Ok(BackendCoin::new(self.amount.parse()?, network_denom)) } - pub fn into_cosmos_coin(self, network_denom: &CosmosDenom) -> Result { - match Decimal::from_str(&self.amount) { - Ok(amount) => Ok(CosmosCoin { - amount, - denom: CosmosDenom::from_str(&self.denom_as_string(network_denom)?)?, - }), - Err(e) => Err(e.into()), - } - } + // pub fn into_cosmos_coin(self, network_denom: &CosmosDenom) -> Result { + // match Decimal::from_str(&self.amount) { + // Ok(amount) => Ok(CosmosCoin { + // amount, + // denom: CosmosDenom::from_str(&self.denom_as_string(network_denom)?)?, + // }), + // Err(e) => Err(e.into()), + // } + // } + // + // pub fn into_cosmwasm_coin( + // self, + // network_denom: &CosmosDenom, + // ) -> Result { + // Ok(CosmWasmCoin { + // denom: self.denom_as_string(network_denom)?, + // amount: Uint128::try_from(self.amount.as_str())?, + // }) + // } +} - pub fn into_cosmwasm_coin( - self, - network_denom: &CosmosDenom, - ) -> Result { - Ok(CosmWasmCoin { - denom: self.denom_as_string(network_denom)?, - amount: Uint128::try_from(self.amount.as_str())?, - }) +impl From for Coin { + fn from(c: BackendCoin) -> Self { + Coin { + amount: c.amount.to_string(), + denom: Denom::from_str(c.denom.as_ref()).unwrap(), + } } } diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 9ef1b56b4e..bac7c09d78 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -1,5 +1,6 @@ use serde::{Serialize, Serializer}; use std::io; +use std::num::ParseIntError; use thiserror::Error; use validator_client::validator_api::error::ValidatorAPIError; use validator_client::{nymd::error::NymdError, ValidatorClientError}; @@ -101,6 +102,8 @@ pub enum BackendError { WalletUnexpectedMultipleAccounts, #[error("Failed to derive address from mnemonic")] FailedToDeriveAddress, + #[error("{0}")] + ValueParseError(#[from] ParseIntError), } impl Serialize for BackendError { diff --git a/nym-wallet/src-tauri/src/network.rs b/nym-wallet/src-tauri/src/network.rs index 6ab4098427..b2fc442e20 100644 --- a/nym-wallet/src-tauri/src/network.rs +++ b/nym-wallet/src-tauri/src/network.rs @@ -1,14 +1,11 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; -use std::fmt; -use std::str::FromStr; -use strum::EnumIter; - use config::defaults::all::Network as ConfigNetwork; use config::defaults::{mainnet, qa, sandbox}; -use validator_client::nymd::Denom; +use serde::{Deserialize, Serialize}; +use std::fmt; +use strum::EnumIter; #[allow(clippy::upper_case_acronyms)] #[cfg_attr(test, derive(ts_rs::TS))] @@ -25,12 +22,12 @@ impl Network { self.to_string().to_lowercase() } - pub fn denom(&self) -> Denom { + pub fn denom(&self) -> &str { match self { // network defaults should be correctly formatted - Network::QA => Denom::from_str(qa::DENOM).unwrap(), - Network::SANDBOX => Denom::from_str(sandbox::DENOM).unwrap(), - Network::MAINNET => Denom::from_str(mainnet::DENOM).unwrap(), + Network::QA => qa::DENOM, + Network::SANDBOX => sandbox::DENOM, + Network::MAINNET => mainnet::DENOM, } } } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 3e70d82fbb..1f1043eebb 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -14,7 +14,6 @@ use itertools::Itertools; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; use strum::IntoEnumIterator; @@ -81,9 +80,9 @@ pub async fn connect_with_mnemonic( pub async fn get_balance( state: tauri::State<'_, Arc>>, ) -> Result { - let denom = state.read().await.current_network().denom(); + let denom = state.read().await.current_network().denom().to_owned(); match nymd_client!(state) - .get_balance(nymd_client!(state).address(), denom.clone()) + .get_balance(nymd_client!(state).address(), denom.parse()?) .await { Ok(Some(coin)) => { @@ -93,7 +92,8 @@ pub async fn get_balance( ); Ok(Balance { coin: coin.clone(), - printable_balance: format!("{} {}", coin.to_major().amount(), &denom.as_ref()[1..]), + // haha, that's so junky : ) + printable_balance: format!("{} {}", coin.to_major().amount(), &denom[1..]), }) } Ok(None) => Err(BackendError::NoBalance( @@ -138,7 +138,7 @@ pub async fn switch_network( Account::new( client.nymd.mixnet_contract_address()?.to_string(), client.nymd.address().to_string(), - denom.try_into()?, + denom.parse()?, ) }; @@ -221,7 +221,7 @@ async fn _connect_with_mnemonic( Some(client) => Ok(Account::new( client.nymd.mixnet_contract_address()?.to_string(), client.nymd.address().to_string(), - default_network.denom().try_into()?, + default_network.denom().parse()?, )), None => Err(BackendError::NetworkNotSupported( config::defaults::DEFAULT_NETWORK, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index e1a0c11e85..ee3c634be9 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -17,8 +17,7 @@ pub async fn bond_gateway( fee: Option, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; + let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?; nymd_client!(state) .bond_gateway(gateway, owner_signature, pledge, fee) .await?; @@ -51,8 +50,7 @@ pub async fn bond_mixnode( fee: Option, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; + let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?; nymd_client!(state) .bond_mixnode(mixnode, owner_signature, pledge, fee) .await?; diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index a3becdfdfe..cedc6cf085 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -4,7 +4,7 @@ use crate::nymd_client; use crate::state::State; use crate::utils::DelegationEvent; use crate::utils::DelegationResult; -use cosmwasm_std::{Coin as CosmWasmCoin, Uint128}; +use cosmwasm_std::Uint128; use mixnet_contract_common::{IdentityKey, PagedDelegatorDelegationsResponse}; use std::sync::Arc; use tokio::sync::RwLock; @@ -31,10 +31,9 @@ pub async fn delegate_to_mixnode( fee: Option, state: tauri::State<'_, Arc>>, ) -> Result { - let denom = state.read().await.current_network().denom(); - let delegation: CosmWasmCoin = amount.into_cosmwasm_coin(&denom)?; + let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?; nymd_client!(state) - .delegate_to_mixnode(identity, &delegation, fee) + .delegate_to_mixnode(identity, delegation.clone(), fee) .await?; Ok(DelegationResult::new( nymd_client!(state).address().as_ref(), diff --git a/nym-wallet/src-tauri/src/operations/mixnet/send.rs b/nym-wallet/src-tauri/src/operations/mixnet/send.rs index 5725f0ca55..ea73967301 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/send.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/send.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; use tokio::sync::RwLock; -use validator_client::nymd::{AccountId, CosmosCoin, Fee, TxResponse}; +use validator_client::nymd::{AccountId, Fee, TxResponse}; #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/tauritxresult.ts"))] @@ -54,17 +54,16 @@ pub async fn send( state: tauri::State<'_, Arc>>, ) -> Result { let address = AccountId::from_str(address)?; - let network_denom = state.read().await.current_network().denom(); - let cosmos_amount: CosmosCoin = amount.clone().into_cosmos_coin(&network_denom)?; + let amount = amount.into_backend_coin(state.read().await.current_network().denom())?; let result = nymd_client!(state) - .send(&address, vec![cosmos_amount], memo, fee) + .send(&address, vec![amount.clone()], memo, fee) .await?; Ok(TauriTxResult::new( result, TransactionDetails { from_address: nymd_client!(state).address().to_string(), to_address: address.to_string(), - amount, + amount: amount.into(), }, )) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index 52f3b4b074..ead6a3fca6 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -21,10 +21,9 @@ pub async fn simulate_update_contract_settings( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nymd.wrap_fundless_contract_execute_message( mixnet_contract, &ExecuteMsg::UpdateContractStateParams(mixnet_contract_settings_params), - vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; diff --git a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs index 2aaa8bda1d..cc6dce3a6e 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs @@ -19,8 +19,7 @@ pub async fn simulate_send( let guard = state.read().await; let to_address = AccountId::from_str(address)?; - let network_denom = guard.current_network().denom(); - let amount = vec![amount.clone().into_cosmos_coin(&network_denom)?]; + let amount = amount.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; let from_address = client.nymd.address().clone(); @@ -30,7 +29,7 @@ pub async fn simulate_send( let msg = MsgSend { from_address, to_address, - amount, + amount: vec![amount.into()], }; let result = client.nymd.simulate(vec![msg]).await?; diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 0fe99ad11c..55d160043e 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -17,8 +17,7 @@ pub async fn simulate_bond_gateway( state: tauri::State<'_, Arc>>, ) -> Result { let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmos_coin(&network_denom)?; + let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; let mixnet_contract = client.nymd.mixnet_contract_address()?; @@ -48,11 +47,9 @@ pub async fn simulate_unbond_gateway( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::UnbondGateway {}, - vec![], - )?; + let msg = client + .nymd + .wrap_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondGateway {})?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -66,8 +63,7 @@ pub async fn simulate_bond_mixnode( state: tauri::State<'_, Arc>>, ) -> Result { let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmos_coin(&network_denom)?; + let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; let mixnet_contract = client.nymd.mixnet_contract_address()?; @@ -96,11 +92,9 @@ pub async fn simulate_unbond_mixnode( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - mixnet_contract, - &ExecuteMsg::UnbondMixnode {}, - vec![], - )?; + let msg = client + .nymd + .wrap_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondMixnode {})?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -117,12 +111,11 @@ pub async fn simulate_update_mixnode( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nymd.wrap_fundless_contract_execute_message( mixnet_contract, &ExecuteMsg::UpdateMixnodeConfig { profit_margin_percent, }, - vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -136,8 +129,7 @@ pub async fn simulate_delegate_to_mixnode( state: tauri::State<'_, Arc>>, ) -> Result { let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let delegation = amount.into_cosmos_coin(&network_denom)?; + let delegation = amount.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; let mixnet_contract = client.nymd.mixnet_contract_address()?; @@ -166,12 +158,11 @@ pub async fn simulate_undelegate_from_mixnode( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nymd.wrap_fundless_contract_execute_message( mixnet_contract, &ExecuteMsg::UndelegateFromMixnode { mix_identity: identity.to_string(), }, - vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index df969cf300..b852a4bf78 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -18,21 +18,19 @@ pub async fn simulate_vesting_bond_gateway( state: tauri::State<'_, Arc>>, ) -> Result { let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&network_denom)?; + let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nymd.wrap_fundless_contract_execute_message( vesting_contract, &ExecuteMsg::BondGateway { gateway, owner_signature, - amount: pledge, + amount: pledge.into(), }, - vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -49,11 +47,9 @@ pub async fn simulate_vesting_unbond_gateway( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::UnbondGateway {}, - vec![], - )?; + let msg = client + .nymd + .wrap_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondGateway {})?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -67,21 +63,19 @@ pub async fn simulate_vesting_bond_mixnode( state: tauri::State<'_, Arc>>, ) -> Result { let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&network_denom)?; + let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nymd.wrap_fundless_contract_execute_message( vesting_contract, &ExecuteMsg::BondMixnode { mix_node: mixnode, owner_signature, - amount: pledge, + amount: pledge.into(), }, - vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -98,11 +92,9 @@ pub async fn simulate_vesting_unbond_mixnode( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( - vesting_contract, - &ExecuteMsg::UnbondMixnode {}, - vec![], - )?; + let msg = client + .nymd + .wrap_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondMixnode {})?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -119,12 +111,11 @@ pub async fn simulate_vesting_update_mixnode( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nymd.wrap_fundless_contract_execute_message( vesting_contract, &ExecuteMsg::UpdateMixnodeConfig { profit_margin_percent, }, - vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -137,17 +128,17 @@ pub async fn simulate_withdraw_vested_coins( state: tauri::State<'_, Arc>>, ) -> Result { let guard = state.read().await; - let network_denom = guard.current_network().denom(); - let amount = amount.into_cosmwasm_coin(&network_denom)?; + let amount = amount.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nymd.wrap_fundless_contract_execute_message( vesting_contract, - &ExecuteMsg::WithdrawVestedCoins { amount }, - vec![], + &ExecuteMsg::WithdrawVestedCoins { + amount: amount.into(), + }, )?; let result = client.nymd.simulate(vec![msg]).await?; diff --git a/nym-wallet/src-tauri/src/operations/vesting/bond.rs b/nym-wallet/src-tauri/src/operations/vesting/bond.rs index 0cfdb654d9..a70b568171 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/bond.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/bond.rs @@ -15,8 +15,7 @@ pub async fn vesting_bond_gateway( fee: Option, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; + let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?; nymd_client!(state) .vesting_bond_gateway(gateway, &owner_signature, pledge, fee) .await?; @@ -49,8 +48,7 @@ pub async fn vesting_bond_mixnode( fee: Option, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let pledge = pledge.into_cosmwasm_coin(&denom)?; + let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?; nymd_client!(state) .vesting_bond_mixnode(mixnode, &owner_signature, pledge, fee) .await?; @@ -63,8 +61,7 @@ pub async fn withdraw_vested_coins( fee: Option, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let denom = state.read().await.current_network().denom(); - let amount = amount.into_cosmwasm_coin(&denom)?; + let amount = amount.into_backend_coin(state.read().await.current_network().denom())?; nymd_client!(state) .withdraw_vested_coins(amount, fee) .await?; diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs index 854b418609..59e669d45f 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -36,10 +36,9 @@ pub async fn vesting_delegate_to_mixnode( fee: Option, state: tauri::State<'_, Arc>>, ) -> Result { - let denom = state.read().await.current_network().denom(); - let delegation = amount.into_cosmwasm_coin(&denom)?; + let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?; nymd_client!(state) - .vesting_delegate_to_mixnode(identity, &delegation, fee) + .vesting_delegate_to_mixnode(identity, delegation.clone(), fee) .await?; Ok(DelegationResult::new( nymd_client!(state).address().as_ref(), From de1df74eb51f490f73b32f6cdcb9f7c11d8d0b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 16:09:37 +0100 Subject: [PATCH 08/15] Simplified the API by removing the generics in favour of explicit Coin type --- .../nymd/cosmwasm_client/signing_client.rs | 49 ++---- .../validator-client/src/nymd/mod.rs | 60 ++++---- .../src/nymd/traits/vesting_signing_client.rs | 144 ++++++++---------- 3 files changed, 107 insertions(+), 146 deletions(-) diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index da8a4ff6ad..aa668676c6 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -8,7 +8,7 @@ use crate::nymd::cosmwasm_client::types::*; use crate::nymd::error::NymdError; use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER}; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use crate::nymd::{GasPrice, TxResponse}; +use crate::nymd::{Coin, GasPrice, TxResponse}; use async_trait::async_trait; use cosmrs::bank::MsgSend; use cosmrs::distribution::MsgWithdrawDelegatorReward; @@ -294,38 +294,17 @@ pub trait SigningCosmWasmClient: CosmWasmClient { }) } - // a helper to automatically infer required types so that you wouldn't need the turbofish - // for normal `execute` if you're not sending any funds with your transaction - async fn fundless_execute( + async fn execute( &self, sender_address: &AccountId, contract_address: &AccountId, msg: &M, fee: Fee, memo: impl Into + Send + 'static, + funds: Vec, ) -> Result where M: ?Sized + Serialize + Sync, - { - self.execute::<_, CosmosCoin, _>(sender_address, contract_address, msg, fee, memo, vec![]) - .await - } - - // the memo had to be extracted into an explicit generic due to: https://github.com/rust-lang/rust/issues/83701 - async fn execute( - &self, - sender_address: &AccountId, - contract_address: &AccountId, - msg: &M, - fee: Fee, - memo: S, - funds: Vec, - ) -> Result - where - M: ?Sized + Serialize + Sync, - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - S: Into + Send + 'static, { let execute_msg = cosmwasm::MsgExecuteContract { sender: sender_address.clone(), @@ -351,7 +330,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { }) } - async fn execute_multiple( + async fn execute_multiple( &self, sender_address: &AccountId, contract_address: &AccountId, @@ -360,9 +339,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { memo: impl Into + Send + 'static, ) -> Result where - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - I: IntoIterator)> + Send, + I: IntoIterator)> + Send, M: Serialize, { let messages = msgs @@ -394,18 +371,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { }) } - async fn send_tokens( + async fn send_tokens( &self, sender_address: &AccountId, recipient_address: &AccountId, - amount: Vec, + amount: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result - where - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - { + ) -> Result { let send_msg = MsgSend { from_address: sender_address.clone(), to_address: recipient_address.clone(), @@ -419,7 +392,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { .check_response() } - async fn send_tokens_multiple( + async fn send_tokens_multiple( &self, sender_address: &AccountId, msgs: I, @@ -427,9 +400,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { memo: impl Into + Send + 'static, ) -> Result where - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - I: IntoIterator)> + Send, + I: IntoIterator)> + Send, { let messages = msgs .into_iter() diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index c1bf428e40..fe038c0406 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -654,17 +654,15 @@ impl NymdClient { } /// Send funds from one address to another - pub async fn send( + pub async fn send( &self, recipient: &AccountId, - amount: Vec, + amount: Vec, memo: impl Into + Send + 'static, fee: Option, ) -> Result where C: SigningCosmWasmClient + Sync, - // this allows you to use both CosmosCoin and Coin - T: Into + Send, { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); self.client @@ -673,16 +671,14 @@ impl NymdClient { } /// Send funds from one address to multiple others - pub async fn send_multiple( + pub async fn send_multiple( &self, - msgs: Vec<(AccountId, Vec)>, + msgs: Vec<(AccountId, Vec)>, memo: impl Into + Send + 'static, fee: Option, ) -> Result where C: SigningCosmWasmClient + Sync, - // this allows you to use both CosmosCoin and Coin - T: Into + Send, { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); self.client @@ -690,18 +686,16 @@ impl NymdClient { .await } - pub async fn execute( + pub async fn execute( &self, contract_address: &AccountId, msg: &M, fee: Fee, memo: impl Into + Send + 'static, - funds: Vec, + funds: Vec, ) -> Result where C: SigningCosmWasmClient + Sync, - // this allows you to use both CosmosCoin and Coin - T: Into + Send, M: ?Sized + Serialize + Sync, { self.client @@ -709,7 +703,7 @@ impl NymdClient { .await } - pub async fn execute_multiple( + pub async fn execute_multiple( &self, contract_address: &AccountId, msgs: I, @@ -718,9 +712,7 @@ impl NymdClient { ) -> Result where C: SigningCosmWasmClient + Sync, - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - I: IntoIterator)> + Send, + I: IntoIterator)> + Send, M: Serialize, { self.client @@ -916,12 +908,13 @@ impl NymdClient { let req = ExecuteMsg::UnbondMixnode {}; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding mixnode from rust!", + vec![], ) .await } @@ -939,12 +932,13 @@ impl NymdClient { let req = ExecuteMsg::UnbondMixnodeOnBehalf { owner }; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding mixnode on behalf from rust!", + vec![], ) .await } @@ -964,12 +958,13 @@ impl NymdClient { profit_margin_percent, }; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Updating mixnode configuration from rust!", + vec![], ) .await } @@ -1081,12 +1076,13 @@ impl NymdClient { mix_identity: mix_identity.to_string(), }; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Removing mixnode delegation from rust!", + vec![], ) .await } @@ -1108,12 +1104,13 @@ impl NymdClient { delegate: delegate.to_string(), }; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Removing mixnode delegation on behalf from rust!", + vec![], ) .await } @@ -1223,12 +1220,13 @@ impl NymdClient { let req = ExecuteMsg::UnbondGateway {}; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding gateway from rust!", + vec![], ) .await } @@ -1247,12 +1245,13 @@ impl NymdClient { let req = ExecuteMsg::UnbondGatewayOnBehalf { owner }; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Unbonding gateway on behalf from rust!", + vec![], ) .await } @@ -1269,12 +1268,13 @@ impl NymdClient { let req = ExecuteMsg::UpdateContractStateParams(new_params); self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Updating contract state from rust!", + vec![], ) .await } @@ -1287,12 +1287,13 @@ impl NymdClient { let req = ExecuteMsg::AdvanceCurrentEpoch {}; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Advance current epoch", + vec![], ) .await } @@ -1305,12 +1306,13 @@ impl NymdClient { let req = ExecuteMsg::ReconcileDelegations {}; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Reconciling delegation events", + vec![], ) .await } @@ -1323,12 +1325,13 @@ impl NymdClient { let req = ExecuteMsg::CheckpointMixnodes {}; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Snapshotting mixnodes", + vec![], ) .await } @@ -1349,12 +1352,13 @@ impl NymdClient { expected_active_set_size, }; self.client - .fundless_execute( + .execute( self.address(), self.mixnet_contract_address()?, &req, fee, "Writing rewarded set", + vec![], ) .await } diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs index b38b53b408..a60f5bea94 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs @@ -4,10 +4,8 @@ pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; use crate::nymd::cosmwasm_client::types::ExecuteResult; use crate::nymd::error::NymdError; -use crate::nymd::{Fee, NymdClient}; +use crate::nymd::{Coin, Fee, NymdClient}; use async_trait::async_trait; -use cosmrs::Coin as CosmosCoin; -use cosmwasm_std::Coin as CosmWasmCoin; use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode}; use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification}; @@ -25,57 +23,57 @@ pub trait VestingSigningClient { fee: Option, ) -> Result; - async fn vesting_bond_gateway + Send>( + async fn vesting_bond_gateway( &self, gateway: Gateway, owner_signature: &str, - pledge: T, + pledge: Coin, fee: Option, ) -> Result; async fn vesting_unbond_gateway(&self, fee: Option) -> Result; - async fn vesting_track_unbond_gateway + Send>( + async fn vesting_track_unbond_gateway( &self, owner: &str, - amount: T, + amount: Coin, fee: Option, ) -> Result; - async fn vesting_bond_mixnode + Send>( + async fn vesting_bond_mixnode( &self, mix_node: MixNode, owner_signature: &str, - pledge: T, + pledge: Coin, fee: Option, ) -> Result; async fn vesting_unbond_mixnode(&self, fee: Option) -> Result; - async fn vesting_track_unbond_mixnode + Send>( + async fn vesting_track_unbond_mixnode( &self, owner: &str, - amount: T, + amount: Coin, fee: Option, ) -> Result; - async fn withdraw_vested_coins + Send>( + async fn withdraw_vested_coins( &self, - amount: T, + amount: Coin, fee: Option, ) -> Result; - async fn vesting_track_undelegation + Send>( + async fn vesting_track_undelegation( &self, address: &str, mix_identity: IdentityKey, - amount: T, + amount: Coin, fee: Option, ) -> Result; - async fn vesting_delegate_to_mixnode<'a, T: Into + Send>( + async fn vesting_delegate_to_mixnode<'a>( &self, mix_identity: IdentityKeyRef<'a>, - amount: T, + amount: Coin, fee: Option, ) -> Result; @@ -85,12 +83,12 @@ pub trait VestingSigningClient { fee: Option, ) -> Result; - async fn create_periodic_vesting_account + Send>( + async fn create_periodic_vesting_account( &self, owner_address: &str, staking_address: Option, vesting_spec: Option, - amount: T, + amount: Coin, fee: Option, ) -> Result; } @@ -107,12 +105,13 @@ impl VestingSigningClient for NymdClient profit_margin_percent, }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UpdateMixnetConfig", + vec![], ) .await } @@ -127,26 +126,24 @@ impl VestingSigningClient for NymdClient address: address.to_string(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UpdateMixnetAddress", + vec![], ) .await } - async fn vesting_bond_gateway( + async fn vesting_bond_gateway( &self, gateway: Gateway, owner_signature: &str, - pledge: T, + pledge: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::BondGateway { gateway, @@ -154,12 +151,13 @@ impl VestingSigningClient for NymdClient amount: pledge.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::BondGateway", + vec![], ) .await } @@ -168,51 +166,47 @@ impl VestingSigningClient for NymdClient let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UnbondGateway {}; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UnbondGateway", + vec![], ) .await } - async fn vesting_track_unbond_gateway( + async fn vesting_track_unbond_gateway( &self, owner: &str, - amount: T, + amount: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUnbondGateway { owner: owner.to_string(), amount: amount.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::TrackUnbondGateway", + vec![], ) .await } - async fn vesting_bond_mixnode( + async fn vesting_bond_mixnode( &self, mix_node: MixNode, owner_signature: &str, - pledge: T, + pledge: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::BondMixnode { mix_node, @@ -220,12 +214,13 @@ impl VestingSigningClient for NymdClient amount: pledge.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::BondMixnode", + vec![], ) .await } @@ -234,72 +229,66 @@ impl VestingSigningClient for NymdClient let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UnbondMixnode {}; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UnbondMixnode", + vec![], ) .await } - async fn vesting_track_unbond_mixnode( + async fn vesting_track_unbond_mixnode( &self, owner: &str, - amount: T, + amount: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUnbondMixnode { owner: owner.to_string(), amount: amount.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::TrackUnbondMixnode", + vec![], ) .await } - async fn withdraw_vested_coins( + async fn withdraw_vested_coins( &self, - amount: T, + amount: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::WithdrawVestedCoins { amount: amount.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::WithdrawVested", + vec![], ) .await } - async fn vesting_track_undelegation( + async fn vesting_track_undelegation( &self, address: &str, mix_identity: IdentityKey, - amount: T, + amount: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUndelegation { owner: address.to_string(), @@ -307,36 +296,35 @@ impl VestingSigningClient for NymdClient amount: amount.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::TrackUndelegation", + vec![], ) .await } - async fn vesting_delegate_to_mixnode<'a, T>( + async fn vesting_delegate_to_mixnode<'a>( &self, mix_identity: IdentityKeyRef<'a>, - amount: T, + amount: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::DelegateToMixnode { mix_identity: mix_identity.into(), amount: amount.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::DelegateToMixnode", + vec![], ) .await } @@ -351,27 +339,25 @@ impl VestingSigningClient for NymdClient mix_identity: mix_identity.into(), }; self.client - .fundless_execute( + .execute( self.address(), self.vesting_contract_address()?, &req, fee, "VestingContract::UndelegateFromMixnode", + vec![], ) .await } - async fn create_periodic_vesting_account( + async fn create_periodic_vesting_account( &self, owner_address: &str, staking_address: Option, vesting_spec: Option, - amount: T, + amount: Coin, fee: Option, - ) -> Result - where - T: Into + Send, - { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::CreateAccount { owner_address: owner_address.to_string(), From 374baa5ec18d21e0be37bf779c5c51be09b06447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 May 2022 16:13:48 +0100 Subject: [PATCH 09/15] Fixed validator api --- validator-api/src/nymd_client.rs | 6 +++--- validator-api/src/rewarded_set_updater/mod.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 7de1c49be6..3e32d40bef 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -18,7 +18,7 @@ use tokio::sync::RwLock; use tokio::time::sleep; use validator_client::nymd::{ hash::{Hash, SHA256_HASH_SIZE}, - CosmWasmClient, CosmosCoin, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, + Coin, CosmWasmClient, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, TendermintTime, }; use validator_client::ValidatorClientError; @@ -326,7 +326,7 @@ impl Client { &self, rewarded_set: Vec, expected_active_set_size: u32, - reward_msgs: Vec<(ExecuteMsg, Vec)>, + reward_msgs: Vec<(ExecuteMsg, Vec)>, ) -> Result<(), RewardingError> where C: SigningCosmWasmClient + Sync, @@ -358,7 +358,7 @@ impl Client { async fn execute_multiple_with_retry( &self, - msgs: Vec<(M, Vec)>, + msgs: Vec<(M, Vec)>, fee: Fee, memo: String, ) -> Result<(), RewardingError> diff --git a/validator-api/src/rewarded_set_updater/mod.rs b/validator-api/src/rewarded_set_updater/mod.rs index 8de7d0c891..9f008348d0 100644 --- a/validator-api/src/rewarded_set_updater/mod.rs +++ b/validator-api/src/rewarded_set_updater/mod.rs @@ -25,7 +25,7 @@ use std::collections::HashSet; use std::time::Duration; use time::OffsetDateTime; use tokio::time::sleep; -use validator_client::nymd::{CosmosCoin, SigningNymdClient}; +use validator_client::nymd::{Coin, SigningNymdClient}; pub(crate) mod error; @@ -120,7 +120,7 @@ impl RewardedSetUpdater { async fn reward_current_rewarded_set( &self, - ) -> Result)>, RewardingError> { + ) -> Result)>, RewardingError> { let to_reward = self.nodes_to_reward().await?; let epoch = self.epoch().await?; @@ -143,7 +143,7 @@ impl RewardedSetUpdater { async fn generate_reward_messages( &self, eligible_mixnodes: &[MixnodeToReward], - ) -> Result)>, RewardingError> { + ) -> Result)>, RewardingError> { cfg_if::cfg_if! { if #[cfg(feature = "no-reward")] { Ok(vec![]) From 83f80f094c9e70b573a9bedc58e2478e743b9926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 31 May 2022 09:06:57 +0200 Subject: [PATCH 10/15] wallet: tweak log text to avoid confusion --- nym-wallet/src-tauri/src/operations/mixnet/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index a84ea2eff4..3b3900872c 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -472,7 +472,7 @@ pub async fn add_account_for_password( account_id: &str, state: tauri::State<'_, Arc>>, ) -> Result { - log::info!("Adding account for the current password: {account_id}"); + log::info!("Adding account: {account_id}"); let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, login id in the wallet From 189b83e7691f1d783967dc325e11dc5f3fd48d9e Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 31 May 2022 11:03:28 +0200 Subject: [PATCH 11/15] Fix uptime integer division, change lambda and sigma from ticked to regular (#1284) * Add more data to reward-estimate response * Fix uptime integer division error * typo * Reify tuple response * Fix uptime calculation * Use lambda and sigma instead of ticked versions for delegator and operator rewards calculation * Changelog --- CHANGELOG.md | 5 ++ .../mixnet-contract/src/mixnode.rs | 81 ++++++++++++------- .../mixnet-contract/src/reward_params.rs | 18 +++-- contracts/mixnet/src/rewards/transactions.rs | 21 +++-- explorer-api/src/mix_node/econ_stats.rs | 2 +- validator-api/src/node_status_api/routes.rs | 14 ++-- .../validator-api-requests/src/models.rs | 2 + 7 files changed, 90 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e395038495..8d8b2387a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,15 @@ - wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint. - mixnet-contract: Replace all naked `-` with `saturating_sub`. - validator-api: add Swagger to document the REST API ([#1249]). +- validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284]) - all: added network compilation target to `--help` (or `--version`) commands ([#1256]). - network-requester: send traffic statistics from all network requesters and receive it in a special network-requester that aggregates the data and exposes it via a rest API ([#1267], [#1278]). ### Fixed +- mixnet-contract: replaced integer division with fixed for performance calculations ([#1284]) +- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284]) +- mixnet-contract: `estimated_delegator_reward` calculation ([#1284]) - vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275]) - mixnet-contract: removed `expect` in `query_delegator_reward` and queries containing invalid proxy address should now return a more human-readable error ([#1257]) - mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258]) @@ -29,6 +33,7 @@ [#1267]: https://github.com/nymtech/nym/pull/1267 [#1275]: https://github.com/nymtech/nym/pull/1275 [#1278]: https://github.com/nymtech/nym/pull/1278 +[#1284]: https://github.com/nymtech/nym/pull/1284 ## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 7447dde7bd..4325727195 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -318,6 +318,14 @@ impl NodeRewardResult { } } +pub struct RewardEstimate { + pub total_node_reward: u64, + pub operator_reward: u64, + pub delegators_reward: u64, + pub node_profit: u64, + pub operator_cost: u64, +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeBond { pub pledge_amount: Coin, @@ -410,63 +418,80 @@ impl MixNodeBond { / U128::from_num(circulating_supply) } + pub fn lambda_ticked(&self, params: &RewardParams) -> U128 { + // Ratio of a bond to the token circulating supply + self.lambda(params).min(params.one_over_k()) + } + pub fn lambda(&self, params: &RewardParams) -> U128 { // Ratio of a bond to the token circulating supply - let pledge_to_circulating_supply_ratio = - self.pledge_to_circulating_supply(params.circulating_supply()); - pledge_to_circulating_supply_ratio.min(params.one_over_k()) + self.pledge_to_circulating_supply(params.circulating_supply()) + } + + pub fn sigma_ticked(&self, params: &RewardParams) -> U128 { + // Ratio of a delegation to the the token circulating supply + self.sigma(params).min(params.one_over_k()) } pub fn sigma(&self, params: &RewardParams) -> U128 { // Ratio of a delegation to the the token circulating supply - let total_bond_to_circulating_supply_ratio = - self.total_bond_to_circulating_supply(params.circulating_supply()); - total_bond_to_circulating_supply_ratio.min(params.one_over_k()) + self.total_bond_to_circulating_supply(params.circulating_supply()) } pub fn estimate_reward( &self, params: &RewardParams, - ) -> Result<(u64, u64, u64), MixnetContractError> { + ) -> Result { let total_node_reward = self .reward(params) .reward() .checked_to_num::() .unwrap_or_default(); + let node_profit = self + .node_profit(params) + .checked_to_num::() + .unwrap_or_default(); + let operator_cost = params + .node + .operator_cost() + .checked_to_num::() + .unwrap_or_default(); let operator_reward = self.operator_reward(params); // Total reward has to be the sum of operator and delegator rewards - let delegators_reward = total_node_reward - operator_reward; + let delegators_reward = node_profit - operator_reward; - Ok(( - total_node_reward.try_into()?, - operator_reward.try_into()?, - delegators_reward.try_into()?, - )) + Ok(RewardEstimate { + total_node_reward: total_node_reward.try_into()?, + operator_reward: operator_reward.try_into()?, + delegators_reward: delegators_reward.try_into()?, + node_profit: node_profit.try_into()?, + operator_cost: operator_cost.try_into()?, + }) } + // keybase://chat/nymtech#dev-core/14473 pub fn reward(&self, params: &RewardParams) -> NodeRewardResult { - let lambda = self.lambda(params); - let sigma = self.sigma(params); + let lambda_ticked = self.lambda_ticked(params); + let sigma_ticked = self.sigma_ticked(params); let reward = params.performance() * params.epoch_reward_pool() - * (sigma * params.omega() - + params.alpha() * lambda * sigma * params.rewarded_set_size()) + * (sigma_ticked * params.omega() + + params.alpha() * lambda_ticked * sigma_ticked * params.rewarded_set_size()) / (ONE + params.alpha()); + // we only need regular lambda and sigma to calculate operator and delegator rewards NodeRewardResult { reward, - lambda, - sigma, + lambda: self.lambda(params), + sigma: self.sigma(params), } } pub fn node_profit(&self, params: &RewardParams) -> U128 { - if self.reward(params).reward() < params.node.operator_cost() { - U128::from_num(0u128) - } else { - self.reward(params).reward() - params.node.operator_cost() - } + self.reward(params) + .reward() + .saturating_sub(params.node.operator_cost()) } pub fn operator_reward(&self, params: &RewardParams) -> u128 { @@ -474,11 +499,9 @@ impl MixNodeBond { if reward.sigma == 0 { return 0; } - let profit = if reward.reward < params.node.operator_cost() { - U128::from_num(0u128) - } else { - reward.reward - params.node.operator_cost() - }; + + let profit = reward.reward.saturating_sub(params.node.operator_cost()); + let operator_base_reward = reward.reward.min(params.node.operator_cost()); // Div by zero checked above let operator_reward = (self.profit_margin() diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs index ec911e7e1d..b033e06c60 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs @@ -42,7 +42,7 @@ impl NodeEpochRewards { } pub fn operator_cost(&self) -> U128 { - U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128) + self.params.operator_cost() } pub fn node_profit(&self) -> U128 { @@ -178,11 +178,15 @@ impl NodeRewardParams { } pub fn operator_cost(&self) -> U128 { - U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128) + self.performance() * U128::from_num(DEFAULT_OPERATOR_INTERVAL_COST) } - pub fn uptime(&self) -> u128 { - self.uptime.u128() + pub fn uptime(&self) -> Uint128 { + self.uptime + } + + pub fn performance(&self) -> U128 { + U128::from_num(self.uptime.u128()) / U128::from_num(100) } pub fn set_reward_blockstamp(&mut self, blockstamp: u64) { @@ -233,7 +237,7 @@ impl RewardParams { } pub fn performance(&self) -> U128 { - U128::from_num(self.node.uptime.u128()) / U128::from_num(100) + self.node.performance() } pub fn set_reward_blockstamp(&mut self, blockstamp: u64) { @@ -256,8 +260,8 @@ impl RewardParams { self.node.reward_blockstamp } - pub fn uptime(&self) -> u128 { - self.node.uptime.u128() + pub fn uptime(&self) -> Uint128 { + self.node.uptime() } pub fn one_over_k(&self) -> U128 { diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 495f3a78d5..2641b2f566 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -449,7 +449,7 @@ pub(crate) fn try_reward_mixnode( let node_delegation = current_bond.total_delegation.amount; // check if it has non-zero uptime - if params.uptime() == 0 { + if params.uptime() == Uint128::zero() { storage::REWARDING_STATUS.save( deps.storage, (epoch.id(), mix_identity.clone()), @@ -1381,7 +1381,7 @@ pub mod tests { let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity) .unwrap() .unwrap(); - let mix_1_uptime = 100; + let mix_1_uptime = 90; let epoch = Interval::init_epoch(env.clone()); save_epoch(&mut deps.storage, &epoch).unwrap(); @@ -1395,7 +1395,7 @@ pub mod tests { params.set_reward_blockstamp(env.block.height); - assert_eq!(params.performance(), U128::from_num(1u32)); + assert_eq!(params.performance(), U128::from_num(0.8999999999999999)); let mix_1_reward_result = mix_1.reward(¶ms); @@ -1407,9 +1407,14 @@ pub mod tests { mix_1_reward_result.lambda(), U128::from_num(0.0000133333333333f64) ); - assert_eq!(mix_1_reward_result.reward().int(), 259114u128); + assert_eq!(mix_1_reward_result.reward().int(), 233202u128); - assert_eq!(mix_1.node_profit(¶ms).int(), 203558u128); + assert_eq!(mix_1.node_profit(¶ms).int(), 183202u128); + + assert_ne!( + mix_1_reward_result.reward(), + mix_1.node_profit(¶ms).int() + ); let mix1_operator_reward = mix_1.operator_reward(¶ms); @@ -1417,9 +1422,9 @@ pub mod tests { let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), ¶ms); - assert_eq!(mix1_operator_reward, 167513); - assert_eq!(mix1_delegator1_reward, 73280); - assert_eq!(mix1_delegator2_reward, 18320); + assert_eq!(mix1_operator_reward, 150761); + assert_eq!(mix1_delegator1_reward, 65952); + assert_eq!(mix1_delegator2_reward, 16488); assert_eq!( mix_1_reward_result.reward().int(), diff --git a/explorer-api/src/mix_node/econ_stats.rs b/explorer-api/src/mix_node/econ_stats.rs index 25765cf8c6..3c526097d4 100644 --- a/explorer-api/src/mix_node/econ_stats.rs +++ b/explorer-api/src/mix_node/econ_stats.rs @@ -36,6 +36,6 @@ pub(crate) async fn retrieve_mixnode_econ_stats( estimated_total_node_reward: reward_estimation.estimated_total_node_reward, estimated_operator_reward: reward_estimation.estimated_operator_reward, estimated_delegators_reward: reward_estimation.estimated_delegators_reward, - current_interval_uptime: reward_estimation.reward_params.node.uptime() as u8, + current_interval_uptime: reward_estimation.reward_params.node.uptime().u128() as u8, }) } diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index 116bb9b190..a03f59661d 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -148,15 +148,13 @@ pub(crate) async fn get_mixnode_reward_estimation( let reward_params = RewardParams::new(reward_params, node_reward_params); match bond.estimate_reward(&reward_params) { - Ok(( - estimated_total_node_reward, - estimated_operator_reward, - estimated_delegators_reward, - )) => { + Ok(reward_estimate) => { let reponse = RewardEstimationResponse { - estimated_total_node_reward, - estimated_operator_reward, - estimated_delegators_reward, + estimated_total_node_reward: reward_estimate.total_node_reward, + estimated_operator_reward: reward_estimate.operator_reward, + estimated_delegators_reward: reward_estimate.delegators_reward, + estimated_node_profit: reward_estimate.node_profit, + estimated_operator_cost: reward_estimate.operator_cost, reward_params, as_at, }; diff --git a/validator-api/validator-api-requests/src/models.rs b/validator-api/validator-api-requests/src/models.rs index 46e849f816..d98b5f798d 100644 --- a/validator-api/validator-api-requests/src/models.rs +++ b/validator-api/validator-api-requests/src/models.rs @@ -58,6 +58,8 @@ pub struct RewardEstimationResponse { pub estimated_total_node_reward: u64, pub estimated_operator_reward: u64, pub estimated_delegators_reward: u64, + pub estimated_node_profit: u64, + pub estimated_operator_cost: u64, pub reward_params: RewardParams, pub as_at: i64, From 2f4be6dedcb4bac06f8612573ba265f1283e58d9 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 31 May 2022 11:06:10 +0200 Subject: [PATCH 12/15] Add claim reward to vesting and mixnet contract (#1292) * Add claim reward to vesting contract * Add compound and claim methods to nymd client * Add TrackReward message * CHANGELOG --- CHANGELOG.md | 3 + .../validator-client/src/nymd/mod.rs | 83 ++++++++ .../src/nymd/traits/vesting_signing_client.rs | 96 +++++++++ .../mixnet-contract/src/events.rs | 27 +++ .../mixnet-contract/src/msg.rs | 11 ++ .../vesting-contract/src/events.rs | 5 + .../vesting-contract/src/messages.rs | 8 + contracts/mixnet/src/contract.rs | 26 +++ contracts/mixnet/src/error.rs | 4 +- contracts/mixnet/src/rewards/transactions.rs | 186 +++++++++++++++++- contracts/vesting/src/contract.rs | 41 +++- .../vesting/src/traits/bonding_account.rs | 2 + .../vesting/src/traits/delegating_account.rs | 6 + .../vesting/src/traits/vesting_account.rs | 1 + .../src/vesting/account/delegating_account.rs | 19 ++ .../account/mixnode_bonding_account.rs | 14 ++ .../src/vesting/account/vesting_account.rs | 7 + 17 files changed, 535 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8b2387a3..927eb0fa5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - wallet: added support for multiple accounts ([#1265]) - wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint. - mixnet-contract: Replace all naked `-` with `saturating_sub`. +- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292]) +- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292]) - validator-api: add Swagger to document the REST API ([#1249]). - validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284]) - all: added network compilation target to `--help` (or `--version`) commands ([#1256]). @@ -34,6 +36,7 @@ [#1275]: https://github.com/nymtech/nym/pull/1275 [#1278]: https://github.com/nymtech/nym/pull/1278 [#1284]: https://github.com/nymtech/nym/pull/1284 +[#1292]: https://github.com/nymtech/nym/pull/1292 ## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04) diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 03adeb8e5b..94a543dcb3 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -786,6 +786,89 @@ impl NymdClient { .await } + pub async fn compound_operator_reward( + &self, + fee: Option, + ) -> Result + where + C: SigningCosmWasmClient + Sync, + { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::CompoundOperatorReward {}; + self.client + .execute( + self.address(), + self.mixnet_contract_address()?, + &req, + fee, + "MixnetContract::CompoundOperatorReward", + vec![], + ) + .await + } + + pub async fn claim_operator_reward(&self, fee: Option) -> Result + where + C: SigningCosmWasmClient + Sync, + { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::ClaimOperatorReward {}; + self.client + .execute( + self.address(), + self.mixnet_contract_address()?, + &req, + fee, + "MixnetContract::ClaimOperatorReward", + vec![], + ) + .await + } + + pub async fn compound_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> Result + where + C: SigningCosmWasmClient + Sync, + { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::CompoundDelegatorReward { mix_identity }; + self.client + .execute( + self.address(), + self.mixnet_contract_address()?, + &req, + fee, + "MixnetContract::CompoundDelegatorReward", + vec![], + ) + .await + } + + pub async fn claim_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> Result + where + C: SigningCosmWasmClient + Sync, + { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::ClaimDelegatorReward { mix_identity }; + self.client + .execute( + self.address(), + self.mixnet_contract_address()?, + &req, + fee, + "MixnetContract::ClaimDelegatorReward", + vec![], + ) + .await + } + /// Announce a mixnode, paying a fee. pub async fn bond_mixnode( &self, diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs index 073cfb7052..3fcf05a218 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs @@ -12,6 +12,28 @@ use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, Vesting #[async_trait] pub trait VestingSigningClient { + async fn vesting_claim_operator_reward( + &self, + fee: Option, + ) -> Result; + + async fn vesting_claim_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> Result; + + async fn vesting_compound_operator_reward( + &self, + fee: Option, + ) -> Result; + + async fn vesting_compound_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> Result; + async fn vesting_update_mixnode_config( &self, profix_margin_percent: u8, @@ -374,4 +396,78 @@ impl VestingSigningClient for NymdClient ) .await } + + async fn vesting_claim_operator_reward( + &self, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = VestingExecuteMsg::ClaimOperatorReward {}; + self.client + .execute( + self.address(), + self.vesting_contract_address()?, + &req, + fee, + "VestingContract::ClaimOperatorReward", + vec![], + ) + .await + } + + async fn vesting_compound_operator_reward( + &self, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = VestingExecuteMsg::CompoundOperatorReward {}; + self.client + .execute( + self.address(), + self.vesting_contract_address()?, + &req, + fee, + "VestingContract::CompoundOperatorReward", + vec![], + ) + .await + } + + async fn vesting_claim_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = VestingExecuteMsg::ClaimDelegatorReward { mix_identity }; + self.client + .execute( + self.address(), + self.vesting_contract_address()?, + &req, + fee, + "VestingContract::ClaimDelegatorReward", + vec![], + ) + .await + } + + async fn vesting_compound_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = VestingExecuteMsg::CompoundDelegatorReward { mix_identity }; + self.client + .execute( + self.address(), + self.vesting_contract_address()?, + &req, + fee, + "VestingContract::CompoundDelegatorReward", + vec![], + ) + .await + } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs index 8464fc6d4c..a2ed8ffe35 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -23,7 +23,9 @@ pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set"; pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval"; pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch"; pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward"; +pub const CLAIM_DELEGATOR_REWARD_EVENT_TYPE: &str = "claim_delegator_reward"; pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward"; +pub const CLAIM_OPERATOR_REWARD_EVENT_TYPE: &str = "claim_operator_reward"; pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes"; // attributes that are used in multiple places @@ -151,6 +153,11 @@ pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Even event.add_attribute(AMOUNT_KEY, amount.to_string()) } +pub fn new_claim_operator_reward_event(owner: &Addr, amount: Uint128) -> Event { + let event = Event::new(CLAIM_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner); + event.add_attribute(AMOUNT_KEY, amount.to_string()) +} + pub fn new_compound_delegator_reward_event( delegator: &Addr, proxy: &Option, @@ -171,6 +178,26 @@ pub fn new_compound_delegator_reward_event( .add_attribute(DELEGATOR_KEY, delegator) } +pub fn new_claim_delegator_reward_event( + delegator: &Addr, + proxy: &Option, + amount: Uint128, + mix_identity: IdentityKeyRef<'_>, +) -> Event { + let mut event = + Event::new(CLAIM_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator); + + if let Some(proxy) = proxy { + event = event.add_attribute(PROXY_KEY, proxy) + } + + // coin implements Display trait and we use that implementation here + event + .add_attribute(AMOUNT_KEY, amount.to_string()) + .add_attribute(DELEGATION_TARGET_KEY, mix_identity) + .add_attribute(DELEGATOR_KEY, delegator) +} + pub fn new_undelegation_event( delegator: &Addr, proxy: &Option, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 8728df83d1..5c49215d9c 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -99,6 +99,17 @@ pub enum ExecuteMsg { }, // AdvanceCurrentInterval {}, AdvanceCurrentEpoch {}, + ClaimOperatorReward {}, + ClaimOperatorRewardOnBehalf { + owner: String, + }, + ClaimDelegatorReward { + mix_identity: IdentityKey, + }, + ClaimDelegatorRewardOnBehalf { + mix_identity: IdentityKey, + owner: String, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs index 85eb009ab2..93ca6814dd 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs @@ -20,6 +20,7 @@ pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixno pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond"; pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond"; pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation"; +pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward"; // attributes that are used in multiple places pub const OWNER_KEY: &str = "owner"; @@ -136,3 +137,7 @@ pub fn new_track_gateway_unbond_event() -> Event { pub fn new_track_undelegation_event() -> Event { Event::new(TRACK_UNDELEGATION_EVENT_TYPE) } + +pub fn new_track_reward_event() -> Event { + Event::new(TRACK_REWARD_EVENT_TYPE) +} diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 4ed3114b4d..e763e164f3 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -49,6 +49,14 @@ impl VestingSpecification { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { + TrackReward { + amount: Coin, + address: String, + }, + ClaimOperatorReward {}, + ClaimDelegatorReward { + mix_identity: String, + }, CompoundDelegatorReward { mix_identity: String, }, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 85a86bcfc3..240055062c 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -283,6 +283,32 @@ pub fn execute( info, ) } + ExecuteMsg::ClaimOperatorReward {} => { + crate::rewards::transactions::try_claim_operator_reward(deps, &env, &info) + } + ExecuteMsg::ClaimOperatorRewardOnBehalf { owner } => { + crate::rewards::transactions::try_claim_operator_reward_on_behalf( + deps, &env, &info, owner, + ) + } + ExecuteMsg::ClaimDelegatorReward { mix_identity } => { + crate::rewards::transactions::try_claim_delegator_reward( + deps, + &env, + &info, + &mix_identity, + ) + } + ExecuteMsg::ClaimDelegatorRewardOnBehalf { + mix_identity, + owner, + } => crate::rewards::transactions::try_claim_delegator_reward_on_behalf( + deps, + &env, + &info, + owner, + &mix_identity, + ), } } diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index b8af736b26..15e082f845 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -153,8 +153,8 @@ pub enum ContractError { #[from] source: MixnetContractError, }, - #[error("No rewards to claim for mixnode {identity} for delegate {delegate}")] - NoRewardsToClaim { identity: String, delegate: String }, + #[error("No rewards to claim for mixnode {identity} for {address}")] + NoRewardsToClaim { identity: String, address: String }, #[error("Epoch not initialized yet!")] EpochNotInitialized, diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 2641b2f566..239b992fc9 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -15,9 +15,13 @@ use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond}; use crate::rewards::helpers; use crate::support::helpers::is_authorized; use config::defaults::DENOM; -use cosmwasm_std::{Addr, Api, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128}; +use cosmwasm_std::{ + coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Order, Response, + Storage, Uint128, +}; use cw_storage_plus::Bound; use mixnet_contract_common::events::{ + new_claim_delegator_reward_event, new_claim_operator_reward_event, new_compound_delegator_reward_event, new_compound_operator_reward_event, new_mix_operator_rewarding_event, new_not_found_mix_operator_rewarding_event, new_too_fresh_bond_mix_operator_rewarding_event, new_zero_uptime_mix_operator_rewarding_event, @@ -27,6 +31,186 @@ use mixnet_contract_common::reward_params::{NodeEpochRewards, NodeRewardParams, use mixnet_contract_common::{Delegation, IdentityKey, RewardingStatus}; use mixnet_contract_common::RewardingResult; +use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; +use vesting_contract_common::one_ucoin; + +// All four of the below methods need to do the following things: +// 1. Calculate currently available rewards +// 2. Send the rewards back to whoever claimed them +// 3. Set the LAST_CLAIMED_HEIGHT to the current height +pub fn try_claim_operator_reward( + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, +) -> Result { + _try_claim_operator_reward(deps.storage, deps.api, env, &info.sender.to_string(), None) +} + +pub fn try_claim_operator_reward_on_behalf( + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + owner: String, +) -> Result { + _try_claim_operator_reward( + deps.storage, + deps.api, + env, + &owner, + Some(info.sender.clone()), + ) +} + +fn _try_claim_operator_reward( + storage: &mut dyn Storage, + api: &dyn Api, + env: &Env, + owner: &str, + proxy: Option, +) -> Result { + let owner = api.addr_validate(owner)?; + + let bond = match crate::mixnodes::storage::mixnodes() + .idx + .owner + .item(storage, owner.clone())? + { + Some(record) => record.1, + None => return Err(ContractError::NoAssociatedMixNodeBond { owner }), + }; + + if proxy != bond.proxy { + return Err(ContractError::ProxyMismatch { + existing: bond + .proxy + .map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), + incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()), + }); + } + + let reward = calculate_operator_reward(storage, api, &owner, &bond)?; + + OPERATOR_REWARD_CLAIMED_HEIGHT.save( + storage, + (owner.to_string(), bond.identity().to_string()), + &env.block.height, + )?; + + if reward.is_zero() { + return Err(ContractError::NoRewardsToClaim { + identity: bond.identity().to_string(), + address: owner.to_string(), + }); + } + + let return_tokens = BankMsg::Send { + to_address: proxy.as_ref().unwrap_or(&owner).to_string(), + amount: coins(reward.u128(), DENOM), + }; + + let mut response = Response::default() + .add_message(return_tokens) + .add_event(new_claim_operator_reward_event(&owner, reward)); + + if let Some(proxy) = proxy { + let msg = Some(VestingContractExecuteMsg::TrackReward { + address: owner.to_string(), + amount: Coin::new(reward.u128(), DENOM), + }); + + let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?; + response = response.add_message(wasm_msg); + } + + Ok(response) +} + +pub fn _try_claim_delegator_reward( + storage: &mut dyn Storage, + api: &dyn Api, + env: &Env, + owner: &str, + mix_identity: &str, + proxy: Option, +) -> Result { + let owner = api.addr_validate(owner)?; + + let key = mixnet_contract_common::delegation::generate_storage_key(&owner, proxy.as_ref()); + let reward = calculate_delegator_reward(storage, api, key.clone(), mix_identity)?; + + DELEGATOR_REWARD_CLAIMED_HEIGHT.save( + storage, + (key, mix_identity.to_string()), + &env.block.height, + )?; + + if reward.is_zero() { + return Err(ContractError::NoRewardsToClaim { + identity: mix_identity.to_string(), + address: owner.to_string(), + }); + } + + let return_tokens = BankMsg::Send { + to_address: proxy.as_ref().unwrap_or(&owner).to_string(), + amount: coins(reward.u128(), DENOM), + }; + + let mut response = + Response::default() + .add_message(return_tokens) + .add_event(new_claim_delegator_reward_event( + &owner, + &proxy, + reward, + mix_identity, + )); + + if let Some(proxy) = proxy { + let msg = Some(VestingContractExecuteMsg::TrackReward { + address: owner.to_string(), + amount: Coin::new(reward.u128(), DENOM), + }); + + let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?; + response = response.add_message(wasm_msg); + } + + Ok(response) +} + +pub fn try_claim_delegator_reward_on_behalf( + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + owner: String, + mix_identity: &str, +) -> Result { + _try_claim_delegator_reward( + deps.storage, + deps.api, + env, + &owner, + mix_identity, + Some(info.sender.clone()), + ) +} + +pub fn try_claim_delegator_reward( + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + mix_identity: &str, +) -> Result { + _try_claim_delegator_reward( + deps.storage, + deps.api, + env, + &info.sender.to_string(), + mix_identity, + None, + ) +} pub fn try_compound_operator_reward_on_behalf( deps: DepsMut, diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index e60fd0e2ef..e40feb27dd 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -13,7 +13,8 @@ use mixnet_contract_common::{Gateway, IdentityKey, MixNode}; use vesting_contract_common::events::{ new_ownership_transfer_event, new_periodic_vesting_account_event, new_staking_address_update_event, new_track_gateway_unbond_event, - new_track_mixnode_unbond_event, new_track_undelegation_event, new_vested_coins_withdraw_event, + new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event, + new_vested_coins_withdraw_event, }; use vesting_contract_common::messages::{ ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification, @@ -46,6 +47,13 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::TrackReward { amount, address } => { + try_track_reward(deps, info, amount, &address) + } + ExecuteMsg::ClaimOperatorReward {} => try_claim_operator_reward(deps, info), + ExecuteMsg::ClaimDelegatorReward { mix_identity } => { + try_claim_delegator_reward(deps, info, mix_identity) + } ExecuteMsg::CompoundDelegatorReward { mix_identity } => { try_compound_delegator_reward(mix_identity, info, deps) } @@ -278,6 +286,20 @@ pub fn try_track_unbond_mixnode( Ok(Response::new().add_event(new_track_mixnode_unbond_event())) } +fn try_track_reward( + deps: DepsMut<'_>, + info: MessageInfo, + amount: Coin, + address: &str, +) -> Result { + if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? { + return Err(ContractError::NotMixnetContract(info.sender)); + } + let account = account_from_address(address, deps.storage, deps.api)?; + account.track_reward(amount, deps.storage)?; + Ok(Response::new().add_event(new_track_reward_event())) +} + fn try_track_undelegation( address: &str, mix_identity: IdentityKey, @@ -314,6 +336,23 @@ fn try_compound_delegator_reward( account.try_compound_delegator_reward(mix_identity, deps.storage) } +fn try_claim_operator_reward( + deps: DepsMut<'_>, + info: MessageInfo, +) -> Result { + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_claim_operator_reward(deps.storage) +} + +fn try_claim_delegator_reward( + deps: DepsMut<'_>, + info: MessageInfo, + mix_identity: String, +) -> Result { + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_claim_delegator_reward(mix_identity, deps.storage) +} + fn try_undelegate_from_mixnode( mix_identity: IdentityKey, info: MessageInfo, diff --git a/contracts/vesting/src/traits/bonding_account.rs b/contracts/vesting/src/traits/bonding_account.rs index ba9a7e2e81..1ce2690df9 100644 --- a/contracts/vesting/src/traits/bonding_account.rs +++ b/contracts/vesting/src/traits/bonding_account.rs @@ -8,6 +8,8 @@ pub trait MixnodeBondingAccount { storage: &dyn Storage, ) -> Result; + fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result; + fn try_bond_mixnode( &self, mix_node: MixNode, diff --git a/contracts/vesting/src/traits/delegating_account.rs b/contracts/vesting/src/traits/delegating_account.rs index c3b188a99a..174a9202ea 100644 --- a/contracts/vesting/src/traits/delegating_account.rs +++ b/contracts/vesting/src/traits/delegating_account.rs @@ -3,6 +3,12 @@ use cosmwasm_std::{Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::IdentityKey; pub trait DelegatingAccount { + fn try_claim_delegator_reward( + &self, + mix_identity: IdentityKey, + storage: &dyn Storage, + ) -> Result; + fn try_compound_delegator_reward( &self, mix_identity: IdentityKey, diff --git a/contracts/vesting/src/traits/vesting_account.rs b/contracts/vesting/src/traits/vesting_account.rs index b1d6687e47..bfb16c6aa9 100644 --- a/contracts/vesting/src/traits/vesting_account.rs +++ b/contracts/vesting/src/traits/vesting_account.rs @@ -73,4 +73,5 @@ pub trait VestingAccount { to_address: Option, storage: &mut dyn Storage, ) -> Result<(), ContractError>; + fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>; } diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index 5ba3b16a5b..ccbacefdcc 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -13,6 +13,25 @@ use vesting_contract_common::one_ucoin; use super::Account; impl DelegatingAccount for Account { + fn try_claim_delegator_reward( + &self, + mix_identity: IdentityKey, + storage: &dyn Storage, + ) -> Result { + let msg = MixnetExecuteMsg::ClaimDelegatorRewardOnBehalf { + owner: self.owner_address().to_string(), + mix_identity, + }; + + let compound_delegator_reward_msg = wasm_execute( + MIXNET_CONTRACT_ADDRESS.load(storage)?, + &msg, + vec![one_ucoin()], + )?; + + Ok(Response::new().add_message(compound_delegator_reward_msg)) + } + fn try_compound_delegator_reward( &self, mix_identity: IdentityKey, diff --git a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index 63d05dff6b..abbfe835ae 100644 --- a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -14,6 +14,20 @@ use vesting_contract_common::PledgeData; use super::Account; impl MixnodeBondingAccount for Account { + fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result { + let msg = MixnetExecuteMsg::ClaimOperatorRewardOnBehalf { + owner: self.owner_address().into_string(), + }; + + let compound_operator_reward_msg = wasm_execute( + MIXNET_CONTRACT_ADDRESS.load(storage)?, + &msg, + vec![one_ucoin()], + )?; + + Ok(Response::new().add_message(compound_operator_reward_msg)) + } + fn try_compound_operator_reward( &self, storage: &dyn Storage, diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs index f4528aded9..46da32e66d 100644 --- a/contracts/vesting/src/vesting/account/vesting_account.rs +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -8,6 +8,13 @@ use vesting_contract_common::{OriginalVestingResponse, Period}; use super::Account; impl VestingAccount for Account { + fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> { + let current_balance = self.load_balance(storage)?; + let new_balance = current_balance + amount.amount; + self.save_balance(new_balance, storage)?; + Ok(()) + } + fn locked_coins( &self, block_time: Option, From 985a38429db88671f209e0ff572c6c6e4dafbaf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 31 May 2022 10:43:17 +0100 Subject: [PATCH 13/15] Fixed up tests and clippy --- clients/credential/src/client.rs | 19 +- .../src/nymd/fee/gas_price.rs | 13 +- .../src/nymd/traits/vesting_signing_client.rs | 2 +- nym-wallet/src-tauri/src/coin.rs | 162 ++++-------------- .../src/operations/mixnet/account.rs | 5 +- 5 files changed, 55 insertions(+), 146 deletions(-) diff --git a/clients/credential/src/client.rs b/clients/credential/src/client.rs index 678a4bdd1d..40f035c6b9 100644 --- a/clients/credential/src/client.rs +++ b/clients/credential/src/client.rs @@ -1,19 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bip39::Mnemonic; -use coconut_bandwidth_contract_common::deposit::DepositData; -use std::str::FromStr; -use url::Url; - use crate::error::Result; use crate::{CONTRACT_ADDRESS, MNEMONIC, NYMD_URL}; - +use bip39::Mnemonic; +use coconut_bandwidth_contract_common::deposit::DepositData; use coconut_bandwidth_contract_common::msg::ExecuteMsg; use network_defaults::DEFAULT_NETWORK; -use validator_client::nymd::{ - AccountId, CosmosCoin, Decimal, Denom, NymdClient, SigningNymdClient, -}; +use std::str::FromStr; +use url::Url; +use validator_client::nymd::{AccountId, Coin, Denom, NymdClient, SigningNymdClient}; pub(crate) struct Client { nymd_client: NymdClient, @@ -55,10 +51,7 @@ impl Client { let req = ExecuteMsg::DepositFunds { data: DepositData::new(info.to_string(), verification_key, encryption_key), }; - let funds = vec![CosmosCoin { - denom: self.denom.clone(), - amount: Decimal::from(amount), - }]; + let funds = vec![Coin::new(amount as u128, self.denom.to_string())]; Ok(self .nymd_client .execute(&self.contract_address, &req, Default::default(), "", funds) diff --git a/common/client-libs/validator-client/src/nymd/fee/gas_price.rs b/common/client-libs/validator-client/src/nymd/fee/gas_price.rs index 9d26f1bd49..c79015ff22 100644 --- a/common/client-libs/validator-client/src/nymd/fee/gas_price.rs +++ b/common/client-libs/validator-client/src/nymd/fee/gas_price.rs @@ -67,9 +67,7 @@ impl FromStr for GasPrice { .parse() .map_err(|_| NymdError::MalformedGasPrice)?; let possible_denom = s.chars().skip(amount_len).collect::(); - let denom = possible_denom - .parse() - .map_err(|_| NymdError::MalformedGasPrice)?; + let denom = possible_denom.trim().to_string(); Ok(GasPrice { amount, denom }) } @@ -110,7 +108,14 @@ mod tests { ); assert!(".25upunk".parse::().is_err()); - assert!("0.025 upunk".parse::().is_err()); + + assert_eq!( + "0.025upunk".parse::().unwrap(), + "0.025 upunk".parse().unwrap() + ); + + let gas: GasPrice = "0.025 upunk ".parse().unwrap(); + assert_eq!("upunk", gas.denom); } #[test] diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs index a60f5bea94..58f4d18a00 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs @@ -371,7 +371,7 @@ impl VestingSigningClient for NymdClient &req, fee, "VestingContract::CreatePeriodicVestingAccount", - vec![amount.into()], + vec![amount], ) .await } diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index 146ca21532..3ecb85cf01 100644 --- a/nym-wallet/src-tauri/src/coin.rs +++ b/nym-wallet/src-tauri/src/coin.rs @@ -152,43 +152,26 @@ impl Coin { } // Helper function that returns the local denom in terms of the specified network denom. - // fn denom_as_string(&self, network_denom: &CosmosDenom) -> Result { - // // Currently there is the widespread assumption that network denomination is always in - // // `Denom::Minor`, and starts with 'u'. - // let network_denom = network_denom.to_string(); - // if !network_denom.starts_with('u') { - // return Err(BackendError::InvalidNetworkDenom(network_denom)); - // } - // - // Ok(match &self.denom { - // Denom::Minor => network_denom, - // Denom::Major => network_denom[1..].to_string(), - // }) - // } + fn denom_as_string(&self, network_denom: &str) -> Result { + // Currently there is the widespread assumption that network denomination is always in + // `Denom::Minor`, and starts with 'u'. + let network_denom = network_denom.to_owned(); + if !network_denom.starts_with('u') { + return Err(BackendError::InvalidNetworkDenom(network_denom)); + } - pub fn into_backend_coin(self, network_denom: &str) -> Result { - Ok(BackendCoin::new(self.amount.parse()?, network_denom)) + Ok(match &self.denom { + Denom::Minor => network_denom, + Denom::Major => network_denom[1..].to_string(), + }) } - // pub fn into_cosmos_coin(self, network_denom: &CosmosDenom) -> Result { - // match Decimal::from_str(&self.amount) { - // Ok(amount) => Ok(CosmosCoin { - // amount, - // denom: CosmosDenom::from_str(&self.denom_as_string(network_denom)?)?, - // }), - // Err(e) => Err(e.into()), - // } - // } - // - // pub fn into_cosmwasm_coin( - // self, - // network_denom: &CosmosDenom, - // ) -> Result { - // Ok(CosmWasmCoin { - // denom: self.denom_as_string(network_denom)?, - // amount: Uint128::try_from(self.amount.as_str())?, - // }) - // } + pub fn into_backend_coin(self, network_denom: &str) -> Result { + Ok(BackendCoin::new( + self.amount.parse()?, + self.denom_as_string(network_denom)?, + )) + } } impl From for Coin { @@ -222,7 +205,6 @@ impl From for Coin { mod test { use super::*; use crate::error::BackendError; - use cosmwasm_std::Coin as CosmWasmCoin; use serde_json::json; use std::convert::TryFrom; use std::str::FromStr; @@ -275,10 +257,10 @@ mod test { #[test] fn network_denom_is_assumed_to_be_in_minor_denom() { - let network_denom = CosmosDenom::from_str("nym").unwrap(); + let network_denom = "nym"; assert!(matches!( Coin::minor("42") - .denom_as_string(&network_denom) + .denom_as_string(network_denom) .unwrap_err(), BackendError::InvalidNetworkDenom { .. } )); @@ -286,51 +268,33 @@ mod test { #[test] fn local_denom_to_interpreted_using_network_denom() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); + let network_denom = "unym"; assert_eq!( - Coin::minor("42").denom_as_string(&network_denom).unwrap(), + Coin::minor("42").denom_as_string(network_denom).unwrap(), "unym", ); assert_eq!( - Coin::major("42").denom_as_string(&network_denom).unwrap(), + Coin::major("42").denom_as_string(network_denom).unwrap(), "nym", ); } #[test] fn coin_to_coin_minor() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); + let network_denom = "unym"; let coin = Coin::minor("42"); - let cosmoswasm_coin = coin.clone().into_cosmwasm_coin(&network_denom).unwrap(); - assert_eq!(cosmoswasm_coin, CosmWasmCoin::new(42, "unym"),); - - let cosmos_coin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - denom: CosmosDenom::from_str("unym").unwrap(), - amount: Decimal::from_str("42").unwrap(), - }, - ); + let backend_coin = coin.clone().into_backend_coin(&network_denom).unwrap(); + assert_eq!(backend_coin, BackendCoin::new(42, "unym")); } #[test] fn coin_to_coin_major() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); + let network_denom = "unym"; let coin = Coin::major("52"); - let cosmoswasm_coin = coin.clone().into_cosmwasm_coin(&network_denom).unwrap(); - assert_eq!(cosmoswasm_coin, CosmWasmCoin::new(52, "nym"),); - - let cosmos_coin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - denom: CosmosDenom::from_str("nym").unwrap(), - amount: Decimal::from_str("52").unwrap(), - }, - ); + let backend_coin = coin.clone().into_backend_coin(network_denom).unwrap(); + assert_eq!(backend_coin, BackendCoin::new(52, "nym")); } fn amounts() -> Vec<&'static str> { @@ -357,74 +321,24 @@ mod test { } #[test] - fn coin_to_cosmoswasm() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); + fn coin_to_backend() { + let network_denom = "unym"; for amount in amounts() { let coin = Coin::minor(amount); - let cosmoswasm_coin: CosmWasmCoin = coin.into_cosmwasm_coin(&network_denom).unwrap(); + let backend_coin = coin.into_backend_coin(network_denom).unwrap(); assert_eq!( - cosmoswasm_coin, - CosmWasmCoin::new(amount.parse::().unwrap(), "unym") - ); - assert_eq!( - Coin::try_from(cosmoswasm_coin).unwrap(), - Coin::minor(amount) + backend_coin, + BackendCoin::new(amount.parse::().unwrap(), "unym") ); + assert_eq!(Coin::try_from(backend_coin).unwrap(), Coin::minor(amount)); let coin = Coin::major(amount); - let cosmoswasm_coin: CosmWasmCoin = coin.into_cosmwasm_coin(&network_denom).unwrap(); + let backend_coin = coin.into_backend_coin(network_denom).unwrap(); assert_eq!( - cosmoswasm_coin, - CosmWasmCoin::new(amount.parse::().unwrap(), "nym") - ); - assert_eq!( - Coin::try_from(cosmoswasm_coin).unwrap(), - Coin::major(amount) + backend_coin, + BackendCoin::new(amount.parse::().unwrap(), "nym") ); + assert_eq!(Coin::try_from(backend_coin).unwrap(), Coin::major(amount)); } } - - #[test] - fn coin_to_cosmos() { - let network_denom = CosmosDenom::from_str("unym").unwrap(); - for amount in amounts() { - let coin = Coin::minor(amount); - let cosmos_coin: CosmosCoin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - amount: Decimal::from_str(amount).unwrap(), - denom: CosmosDenom::from_str("unym").unwrap() - } - ); - assert_eq!(Coin::try_from(cosmos_coin).unwrap(), Coin::minor(amount)); - - let coin = Coin::major(amount); - let cosmos_coin: CosmosCoin = coin.into_cosmos_coin(&network_denom).unwrap(); - assert_eq!( - cosmos_coin, - CosmosCoin { - amount: Decimal::from_str(amount).unwrap(), - denom: CosmosDenom::from_str("nym").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")); - } } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 1f1043eebb..a84331873b 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -86,10 +86,7 @@ pub async fn get_balance( .await { Ok(Some(coin)) => { - let coin = Coin::new( - &coin.amount.to_string(), - &Denom::from_str(&coin.denom.to_string())?, - ); + let coin = Coin::new(&coin.amount.to_string(), &Denom::from_str(&coin.denom)?); Ok(Balance { coin: coin.clone(), // haha, that's so junky : ) From 11122635f9d48698b21b24fd532479c13697c428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 31 May 2022 11:37:10 +0100 Subject: [PATCH 14/15] Refactored missed coin-generic API methods --- .../nymd/cosmwasm_client/signing_client.rs | 22 +++++---------- .../validator-client/src/nymd/mod.rs | 18 ++---------- .../src/operations/simulate/admin.rs | 3 +- .../src/operations/simulate/mixnet.rs | 22 +++++++++------ .../src/operations/simulate/vesting.rs | 28 ++++++++++++------- 5 files changed, 43 insertions(+), 50 deletions(-) diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index aa668676c6..1f690d6812 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -17,7 +17,7 @@ use cosmrs::rpc::endpoint::broadcast; use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest}; use cosmrs::staking::{MsgDelegate, MsgUndelegate}; use cosmrs::tx::{self, Msg, SignDoc, SignerInfo}; -use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin as CosmosCoin, Tx}; +use cosmrs::{cosmwasm, rpc, AccountId, Any, Tx}; use log::debug; use serde::Serialize; use sha2::Digest; @@ -420,18 +420,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { .check_response() } - async fn delegate_tokens( + async fn delegate_tokens( &self, delegator_address: &AccountId, validator_address: &AccountId, - amount: T, + amount: Coin, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result - where - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - { + ) -> Result { let delegate_msg = MsgDelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), @@ -445,18 +441,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { .check_response() } - async fn undelegate_tokens( + async fn undelegate_tokens( &self, delegator_address: &AccountId, validator_address: &AccountId, - amount: T, + amount: Coin, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result - where - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - { + ) -> Result { let undelegate_msg = MsgUndelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 5de66f7c25..19d17f390a 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -174,29 +174,15 @@ impl NymdClient { self.simulated_gas_multiplier = multiplier; } - pub fn wrap_fundless_contract_execute_message( + pub fn wrap_contract_execute_message( &self, contract_address: &AccountId, msg: &M, + funds: Vec, ) -> Result where C: SigningCosmWasmClient, M: ?Sized + Serialize, - { - self.wrap_contract_execute_message::<_, CosmosCoin>(contract_address, msg, vec![]) - } - - pub fn wrap_contract_execute_message( - &self, - contract_address: &AccountId, - msg: &M, - funds: Vec, - ) -> Result - where - C: SigningCosmWasmClient, - // this allows you to use both CosmosCoin and Coin - T: Into + Send, - M: ?Sized + Serialize, { Ok(cosmwasm::MsgExecuteContract { sender: self.address().clone(), diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index c7006fcf46..cbfa8e39f9 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -21,9 +21,10 @@ pub async fn simulate_update_contract_settings( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_fundless_contract_execute_message( + let msg = client.nymd.wrap_contract_execute_message( mixnet_contract, &ExecuteMsg::UpdateContractStateParams(mixnet_contract_settings_params), + vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 8b62a1ec74..37d48859aa 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -47,9 +47,11 @@ pub async fn simulate_unbond_gateway( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client - .nymd - .wrap_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondGateway {})?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::UnbondGateway {}, + vec![], + )?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -92,9 +94,11 @@ pub async fn simulate_unbond_mixnode( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client - .nymd - .wrap_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondMixnode {})?; + let msg = client.nymd.wrap_contract_execute_message( + mixnet_contract, + &ExecuteMsg::UnbondMixnode {}, + vec![], + )?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -111,11 +115,12 @@ pub async fn simulate_update_mixnode( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_fundless_contract_execute_message( + let msg = client.nymd.wrap_contract_execute_message( mixnet_contract, &ExecuteMsg::UpdateMixnodeConfig { profit_margin_percent, }, + vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -158,11 +163,12 @@ pub async fn simulate_undelegate_from_mixnode( let mixnet_contract = client.nymd.mixnet_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_fundless_contract_execute_message( + let msg = client.nymd.wrap_contract_execute_message( mixnet_contract, &ExecuteMsg::UndelegateFromMixnode { mix_identity: identity.to_string(), }, + vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index bb9823ea84..c72b7be820 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -24,13 +24,14 @@ pub async fn simulate_vesting_bond_gateway( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_fundless_contract_execute_message( + let msg = client.nymd.wrap_contract_execute_message( vesting_contract, &ExecuteMsg::BondGateway { gateway, owner_signature, amount: pledge.into(), }, + vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -47,9 +48,11 @@ pub async fn simulate_vesting_unbond_gateway( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client - .nymd - .wrap_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondGateway {})?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::UnbondGateway {}, + vec![], + )?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -69,13 +72,14 @@ pub async fn simulate_vesting_bond_mixnode( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_fundless_contract_execute_message( + let msg = client.nymd.wrap_contract_execute_message( vesting_contract, &ExecuteMsg::BondMixnode { mix_node: mixnode, owner_signature, amount: pledge.into(), }, + vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -92,9 +96,11 @@ pub async fn simulate_vesting_unbond_mixnode( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client - .nymd - .wrap_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondMixnode {})?; + let msg = client.nymd.wrap_contract_execute_message( + vesting_contract, + &ExecuteMsg::UnbondMixnode {}, + vec![], + )?; let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) @@ -111,11 +117,12 @@ pub async fn simulate_vesting_update_mixnode( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_fundless_contract_execute_message( + let msg = client.nymd.wrap_contract_execute_message( vesting_contract, &ExecuteMsg::UpdateMixnodeConfig { profit_margin_percent, }, + vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; @@ -134,11 +141,12 @@ pub async fn simulate_withdraw_vested_coins( let vesting_contract = client.nymd.vesting_contract_address()?; let gas_price = client.nymd.gas_price().clone(); - let msg = client.nymd.wrap_fundless_contract_execute_message( + let msg = client.nymd.wrap_contract_execute_message( vesting_contract, &ExecuteMsg::WithdrawVestedCoins { amount: amount.into(), }, + vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; From 241f0cf93ac303b4e67460fb82638a072417d6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 31 May 2022 11:48:29 +0100 Subject: [PATCH 15/15] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 927eb0fa5c..b8334cd702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ - mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258]) - mixnode, gateway: attempting to determine reconnection backoff to persistently failing mixnode could result in a crash ([#1260]) +### Changed + +- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]] + [#1258]: https://github.com/nymtech/nym/pull/1258 [#1249]: https://github.com/nymtech/nym/pull/1249 [#1256]: https://github.com/nymtech/nym/pull/1256 @@ -37,6 +41,7 @@ [#1278]: https://github.com/nymtech/nym/pull/1278 [#1284]: https://github.com/nymtech/nym/pull/1284 [#1292]: https://github.com/nymtech/nym/pull/1292 +[#1295]: https://github.com/nymtech/nym/pull/1295 ## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04)