From 6afecbddfaaa8dc7bc01970796fa7303508b339d Mon Sep 17 00:00:00 2001 From: gala1234 Date: Thu, 26 May 2022 12:02:44 +0200 Subject: [PATCH 01/24] explorer: mook stake saturation response from API --- explorer/src/components/MixNodes/index.ts | 3 +++ explorer/src/context/main.tsx | 6 ++++++ explorer/src/pages/Mixnodes/index.tsx | 17 +++++++++++++++++ explorer/src/typeDefs/explorer-api.ts | 1 + 4 files changed, 27 insertions(+) diff --git a/explorer/src/components/MixNodes/index.ts b/explorer/src/components/MixNodes/index.ts index b9e696e785..298f449e87 100644 --- a/explorer/src/components/MixNodes/index.ts +++ b/explorer/src/components/MixNodes/index.ts @@ -13,6 +13,7 @@ export type MixnodeRowType = { layer: string; profit_percentage: string; avg_uptime: string; + stake_saturation: string; }; export function mixnodeToGridRow(arrayOfMixnodes?: MixNodeResponse): MixnodeRowType[] { @@ -25,6 +26,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem): const totalBond = pledge + delegations; const selfPercentage = ((pledge * 100) / totalBond).toFixed(2); const profitPercentage = item.mix_node.profit_margin_percent || 0; + const stakeSaturation = item.stake_saturation || ''; return { id: item.owner, status: item.status, @@ -37,5 +39,6 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem): layer: item?.layer || '', profit_percentage: `${profitPercentage}%`, avg_uptime: `${item.avg_uptime}%` || '-', + stake_saturation: typeof stakeSaturation === 'number' ? `${(stakeSaturation * 100).toFixed(2)} %` : '-', }; } diff --git a/explorer/src/context/main.tsx b/explorer/src/context/main.tsx index 4663cd2644..1d50112b91 100644 --- a/explorer/src/context/main.tsx +++ b/explorer/src/context/main.tsx @@ -81,6 +81,12 @@ export const MainContextProvider: React.FC = ({ children }) => { setMixnodes((d) => ({ ...d, isLoading: true })); try { const data = status ? await Api.fetchMixnodesActiveSetByStatus(status) : await Api.fetchMixnodes(); + // TODO remove hard coded API response + data.map((item) => { + const node = item; + node.stake_saturation = 0.32; + return node; + }); setMixnodes({ data, isLoading: false }); } catch (error) { setMixnodes({ diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx index 49919b00d0..a8f15deafe 100644 --- a/explorer/src/pages/Mixnodes/index.tsx +++ b/explorer/src/pages/Mixnodes/index.tsx @@ -248,6 +248,23 @@ export const PageMixnodes: React.FC = () => { ), }, + { + field: 'stake_saturation', + headerName: 'Stake Saturation', + renderHeader: () => , + headerClassName: 'MuiDataGrid-header-override', + width: 175, + headerAlign: 'left', + renderCell: (params: GridRenderCellParams) => ( + + {params.value} + + ), + }, ]; const handlePageSize = (event: SelectChangeEvent) => { diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 36dd210181..1ff353839c 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -84,6 +84,7 @@ export interface MixNodeResponseItem { }; mix_node: MixNode; avg_uptime: number; + stake_saturation: number; } export type MixNodeResponse = MixNodeResponseItem[]; From 7dd11d46027b7455334fce968738d7bb22ad2188 Mon Sep 17 00:00:00 2001 From: gala1234 Date: Thu, 26 May 2022 12:25:43 +0200 Subject: [PATCH 02/24] explorer: stake saturation colour --- explorer/src/components/MixNodes/index.ts | 6 +++--- explorer/src/pages/Mixnodes/index.tsx | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/explorer/src/components/MixNodes/index.ts b/explorer/src/components/MixNodes/index.ts index 298f449e87..55a6b7c080 100644 --- a/explorer/src/components/MixNodes/index.ts +++ b/explorer/src/components/MixNodes/index.ts @@ -13,7 +13,7 @@ export type MixnodeRowType = { layer: string; profit_percentage: string; avg_uptime: string; - stake_saturation: string; + stake_saturation: number; }; export function mixnodeToGridRow(arrayOfMixnodes?: MixNodeResponse): MixnodeRowType[] { @@ -26,7 +26,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem): const totalBond = pledge + delegations; const selfPercentage = ((pledge * 100) / totalBond).toFixed(2); const profitPercentage = item.mix_node.profit_margin_percent || 0; - const stakeSaturation = item.stake_saturation || ''; + const stakeSaturation = typeof item.stake_saturation === 'number' ? item.stake_saturation * 100 : 0; return { id: item.owner, status: item.status, @@ -39,6 +39,6 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem): layer: item?.layer || '', profit_percentage: `${profitPercentage}%`, avg_uptime: `${item.avg_uptime}%` || '-', - stake_saturation: typeof stakeSaturation === 'number' ? `${(stakeSaturation * 100).toFixed(2)} %` : '-', + stake_saturation: stakeSaturation, }; } diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx index a8f15deafe..0a8014c67e 100644 --- a/explorer/src/pages/Mixnodes/index.tsx +++ b/explorer/src/pages/Mixnodes/index.tsx @@ -257,11 +257,15 @@ export const PageMixnodes: React.FC = () => { headerAlign: 'left', renderCell: (params: GridRenderCellParams) => ( 100 ? theme.palette.warning.main : 'inherit', + }} component={RRDLink} to={`/network-components/mixnode/${params.row.identity_key}`} > - {params.value} + {`${params.value.toFixed(2)} %`} ), }, From 296ed47ba4b6cf3756311b43b5eea86d708eea8e Mon Sep 17 00:00:00 2001 From: gala1234 Date: Thu, 26 May 2022 12:32:21 +0200 Subject: [PATCH 03/24] explorer: fixing colour --- explorer/src/context/main.tsx | 2 +- explorer/src/pages/Mixnodes/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/explorer/src/context/main.tsx b/explorer/src/context/main.tsx index 1d50112b91..d00d7fd1a0 100644 --- a/explorer/src/context/main.tsx +++ b/explorer/src/context/main.tsx @@ -84,7 +84,7 @@ export const MainContextProvider: React.FC = ({ children }) => { // TODO remove hard coded API response data.map((item) => { const node = item; - node.stake_saturation = 0.32; + node.stake_saturation = 3.2; return node; }); setMixnodes({ data, isLoading: false }); diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx index 0a8014c67e..54de0641af 100644 --- a/explorer/src/pages/Mixnodes/index.tsx +++ b/explorer/src/pages/Mixnodes/index.tsx @@ -260,7 +260,7 @@ export const PageMixnodes: React.FC = () => { sx={{ ...getCellStyles(theme, params.row), textAlign: 'left', - colour: params.value > 100 ? theme.palette.warning.main : 'inherit', + color: params.value > 100 ? theme.palette.warning.main : 'inherit', }} component={RRDLink} to={`/network-components/mixnode/${params.row.identity_key}`} 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 04/24] 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 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 05/24] 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 06/24] 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 07/24] 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 58b5389ed9042a72ce104269edb42da153a42b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 31 May 2022 14:10:19 +0100 Subject: [PATCH 08/24] Consolidate validator-client coin (#1295) * Created nymd internal coin * Additional From implementations plus a constructor * try_add * Changed client API to use the new coin type * CoinConverter trait * Made wallet compilable with the recent changes * Simplified the API by removing the generics in favour of explicit Coin type * Fixed validator api * Fixed up tests and clippy * Refactored missed coin-generic API methods * changelog --- CHANGELOG.md | 5 + clients/credential/src/client.rs | 19 +- .../validator-client/src/nymd/coin.rs | 130 ++++++++++++++ .../src/nymd/cosmwasm_client/client.rs | 21 +-- .../nymd/cosmwasm_client/signing_client.rs | 16 +- .../src/nymd/cosmwasm_client/types.rs | 21 ++- .../src/nymd/fee/gas_price.rs | 27 ++- .../validator-client/src/nymd/mod.rs | 89 ++++------ .../src/nymd/traits/vesting_query_client.rs | 21 ++- .../src/nymd/traits/vesting_signing_client.rs | 25 +-- nym-wallet/src-tauri/src/coin.rs | 163 +++++------------- nym-wallet/src-tauri/src/error.rs | 3 + nym-wallet/src-tauri/src/network.rs | 17 +- .../src/operations/mixnet/account.rs | 17 +- .../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/cosmos.rs | 5 +- .../src/operations/simulate/mixnet.rs | 9 +- .../src/operations/simulate/vesting.rs | 17 +- .../src-tauri/src/operations/vesting/bond.rs | 9 +- .../src/operations/vesting/delegate.rs | 5 +- validator-api/src/nymd_client.rs | 6 +- validator-api/src/rewarded_set_updater/mod.rs | 6 +- 24 files changed, 347 insertions(+), 306 deletions(-) create mode 100644 common/client-libs/validator-client/src/nymd/coin.rs 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) 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/coin.rs b/common/client-libs/validator-client/src/nymd/coin.rs new file mode 100644 index 0000000000..77451032ac --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/coin.rs @@ -0,0 +1,130 @@ +// 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, +} + +impl Coin { + pub fn new>(amount: u128, denom: S) -> Self { + Coin { + amount, + denom: denom.into(), + } + } + + 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 { + 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 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, + } + } +} + +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(), + } + } +} 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..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 @@ -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; @@ -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, Tx}; use log::debug; use serde::Serialize; use sha2::Digest; @@ -310,7 +310,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()))?; @@ -349,7 +349,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())) @@ -382,7 +382,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { 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()))?; @@ -408,7 +408,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())) @@ -431,7 +431,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { 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()))?; @@ -452,7 +452,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { 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..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 @@ -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(), } } @@ -63,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 }) } @@ -106,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/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 94a543dcb3..19d17f390a 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; @@ -43,9 +44,11 @@ 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}; +pub mod coin; pub mod cosmwasm_client; pub mod error; pub mod fee; @@ -175,7 +178,7 @@ impl NymdClient { &self, contract_address: &AccountId, msg: &M, - funds: Vec, + funds: Vec, ) -> Result where C: SigningCosmWasmClient, @@ -185,7 +188,7 @@ impl NymdClient { sender: self.address().clone(), contract: contract_address.clone(), msg: serde_json::to_vec(msg)?, - funds, + funds: funds.into_iter().map(Into::into).collect(), }) } @@ -265,7 +268,7 @@ impl NymdClient { &self, address: &AccountId, denom: Denom, - ) -> Result, NymdError> + ) -> Result, NymdError> where C: CosmWasmClient + Sync, { @@ -640,7 +643,7 @@ impl NymdClient { pub async fn send( &self, recipient: &AccountId, - amount: Vec, + amount: Vec, memo: impl Into + Send + 'static, fee: Option, ) -> Result @@ -656,7 +659,7 @@ impl NymdClient { /// Send funds from one address to multiple others pub async fn send_multiple( &self, - msgs: Vec<(AccountId, Vec)>, + msgs: Vec<(AccountId, Vec)>, memo: impl Into + Send + 'static, fee: Option, ) -> Result @@ -675,7 +678,7 @@ impl NymdClient { msg: &M, fee: Fee, memo: impl Into + Send + 'static, - funds: Vec, + funds: Vec, ) -> Result where C: SigningCosmWasmClient + Sync, @@ -695,7 +698,7 @@ impl NymdClient { ) -> Result where C: SigningCosmWasmClient + Sync, - I: IntoIterator)> + Send, + I: IntoIterator)> + Send, M: Serialize, { self.client @@ -893,7 +896,7 @@ impl NymdClient { &req, fee, "Bonding mixnode from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -924,7 +927,7 @@ impl NymdClient { &req, fee, "Bonding mixnode on behalf from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -940,7 +943,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)| { ( @@ -949,7 +952,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(); @@ -980,7 +983,7 @@ impl NymdClient { &req, fee, "Unbonding mixnode from rust!", - Vec::new(), + vec![], ) .await } @@ -1004,7 +1007,7 @@ impl NymdClient { &req, fee, "Unbonding mixnode on behalf from rust!", - Vec::new(), + vec![], ) .await } @@ -1030,7 +1033,7 @@ impl NymdClient { &req, fee, "Updating mixnode configuration from rust!", - Vec::new(), + vec![], ) .await } @@ -1039,7 +1042,7 @@ impl NymdClient { pub async fn delegate_to_mixnode( &self, mix_identity: &str, - amount: &Coin, + amount: Coin, fee: Option, ) -> Result where @@ -1057,7 +1060,7 @@ impl NymdClient { &req, fee, "Delegating to mixnode from rust!", - vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)], + vec![amount], ) .await } @@ -1068,7 +1071,7 @@ impl NymdClient { &self, mix_identity: &str, delegate: &str, - amount: &Coin, + amount: Coin, fee: Option, ) -> Result where @@ -1087,7 +1090,7 @@ impl NymdClient { &req, fee, "Delegating to mixnode on behalf from rust!", - vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)], + vec![amount], ) .await } @@ -1103,7 +1106,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| { ( @@ -1111,7 +1114,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(); @@ -1148,7 +1151,7 @@ impl NymdClient { &req, fee, "Removing mixnode delegation from rust!", - Vec::new(), + vec![], ) .await } @@ -1176,7 +1179,7 @@ impl NymdClient { &req, fee, "Removing mixnode delegation on behalf from rust!", - Vec::new(), + vec![], ) .await } @@ -1205,7 +1208,7 @@ impl NymdClient { &req, fee, "Bonding gateway from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -1236,7 +1239,7 @@ impl NymdClient { &req, fee, "Bonding gateway on behalf from rust!", - vec![cosmwasm_coin_to_cosmos_coin(pledge)], + vec![pledge], ) .await } @@ -1252,7 +1255,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)| { ( @@ -1261,7 +1264,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(); @@ -1292,7 +1295,7 @@ impl NymdClient { &req, fee, "Unbonding gateway from rust!", - Vec::new(), + vec![], ) .await } @@ -1317,7 +1320,7 @@ impl NymdClient { &req, fee, "Unbonding gateway on behalf from rust!", - Vec::new(), + vec![], ) .await } @@ -1340,7 +1343,7 @@ impl NymdClient { &req, fee, "Updating contract state from rust!", - Vec::new(), + vec![], ) .await } @@ -1359,7 +1362,7 @@ impl NymdClient { &req, fee, "Advance current epoch", - Vec::new(), + vec![], ) .await } @@ -1378,7 +1381,7 @@ impl NymdClient { &req, fee, "Reconciling delegation events", - Vec::new(), + vec![], ) .await } @@ -1397,7 +1400,7 @@ impl NymdClient { &req, fee, "Snapshotting mixnodes", - Vec::new(), + vec![], ) .await } @@ -1424,24 +1427,8 @@ impl NymdClient { &req, fee, "Writing rewarded set", - Vec::new(), + vec![], ) .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 3fcf05a218..163b6957db 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,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::{cosmwasm_coin_to_cosmos_coin, Fee, NymdClient}; +use crate::nymd::{Coin, Fee, NymdClient}; use async_trait::async_trait; -use cosmwasm_std::Coin; use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode}; use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification}; @@ -96,7 +95,7 @@ pub trait VestingSigningClient { async fn vesting_delegate_to_mixnode<'a>( &self, mix_identity: IdentityKeyRef<'a>, - amount: &Coin, + amount: Coin, fee: Option, ) -> Result; @@ -171,7 +170,7 @@ impl VestingSigningClient for NymdClient let req = VestingExecuteMsg::BondGateway { gateway, owner_signature: owner_signature.to_string(), - amount: pledge, + amount: pledge.into(), }; self.client .execute( @@ -209,7 +208,7 @@ impl VestingSigningClient for NymdClient 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( @@ -234,7 +233,7 @@ impl VestingSigningClient for NymdClient let req = VestingExecuteMsg::BondMixnode { mix_node, owner_signature: owner_signature.to_string(), - amount: pledge, + amount: pledge.into(), }; self.client .execute( @@ -272,7 +271,7 @@ impl VestingSigningClient for NymdClient 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( @@ -291,7 +290,9 @@ impl VestingSigningClient for NymdClient fee: Option, ) -> Result { 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( self.address(), @@ -314,7 +315,7 @@ impl VestingSigningClient for NymdClient let req = VestingExecuteMsg::TrackUndelegation { owner: address.to_string(), mix_identity, - amount, + amount: amount.into(), }; self.client .execute( @@ -330,13 +331,13 @@ impl VestingSigningClient for NymdClient async fn vesting_delegate_to_mixnode<'a>( &self, mix_identity: IdentityKeyRef<'a>, - amount: &Coin, + amount: Coin, fee: Option, ) -> Result { 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( @@ -392,7 +393,7 @@ impl VestingSigningClient for NymdClient &req, fee, "VestingContract::CreatePeriodicVestingAccount", - vec![cosmwasm_coin_to_cosmos_coin(amount)], + vec![amount], ) .await } diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index 83ed92c807..1903e869f9 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,10 +152,10 @@ 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 { + 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_string(); + let network_denom = network_denom.to_owned(); if !network_denom.starts_with('u') { return Err(BackendError::InvalidNetworkDenom(network_denom)); } @@ -168,24 +166,20 @@ impl Coin { }) } - 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_backend_coin(self, network_denom: &str) -> Result { + Ok(BackendCoin::new( + self.amount.parse()?, + self.denom_as_string(network_denom)?, + )) } +} - 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(), + } } } @@ -211,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; @@ -264,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 { .. } )); @@ -275,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> { @@ -346,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/error.rs b/nym-wallet/src-tauri/src/error.rs index 3f6181e008..e0fb1e565b 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 eae5058519..9f0e21f990 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 3b3900872c..f48cffe515 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,19 +80,17 @@ 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)) => { - 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(), - 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 +135,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 +218,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 938896e769..e940ff60ac 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 ebd4ebec54..c559b18e43 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; @@ -29,10 +29,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 a50730ab5a..52a7258dfc 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/cosmos.rs b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs index e2647c69ea..4f2078713a 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 014e003cbc..37d48859aa 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()?; @@ -66,8 +65,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()?; @@ -136,8 +134,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()?; diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index d424777496..c72b7be820 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -18,8 +18,7 @@ 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()?; @@ -30,7 +29,7 @@ pub async fn simulate_vesting_bond_gateway( &ExecuteMsg::BondGateway { gateway, owner_signature, - amount: pledge, + amount: pledge.into(), }, vec![], )?; @@ -67,8 +66,7 @@ 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()?; @@ -79,7 +77,7 @@ pub async fn simulate_vesting_bond_mixnode( &ExecuteMsg::BondMixnode { mix_node: mixnode, owner_signature, - amount: pledge, + amount: pledge.into(), }, vec![], )?; @@ -137,8 +135,7 @@ 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()?; @@ -146,7 +143,9 @@ pub async fn simulate_withdraw_vested_coins( let msg = client.nymd.wrap_contract_execute_message( vesting_contract, - &ExecuteMsg::WithdrawVestedCoins { amount }, + &ExecuteMsg::WithdrawVestedCoins { + amount: amount.into(), + }, vec![], )?; diff --git a/nym-wallet/src-tauri/src/operations/vesting/bond.rs b/nym-wallet/src-tauri/src/operations/vesting/bond.rs index 8a5b103cea..d06842f067 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 94b6c654e8..a212afe7f1 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -34,10 +34,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(), 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 9f51c60bacd320bce2726237101a2747dc41a3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 31 May 2022 15:52:26 +0100 Subject: [PATCH 09/24] Wallet simulate bandaid (#1296) * Removed Add/Sub that somehow got brought back in a merge * Created 'get_old_and_incorrect_hardcoded_fee' to make wallet as it did before * Brought back all Operation variants just in case --- nym-wallet/src-tauri/src/coin.rs | 45 ------------ nym-wallet/src-tauri/src/main.rs | 1 + nym-wallet/src-tauri/src/utils.rs | 113 +++++++++++++++++++++++++++++ nym-wallet/src/requests/queries.ts | 8 +- 4 files changed, 118 insertions(+), 49 deletions(-) diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index 1903e869f9..9f4c1d7f3a 100644 --- a/nym-wallet/src-tauri/src/coin.rs +++ b/nym-wallet/src-tauri/src/coin.rs @@ -4,7 +4,6 @@ use crate::error::BackendError; use crate::network::Network; 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; @@ -54,50 +53,6 @@ pub struct Coin { denom: Denom, } -// Allows adding minor and major denominations, output will have the LHS denom. -impl Add for Coin { - 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, - } - } -} - -// Allows adding minor and major denominations, output will have the LHS denom. -impl Sub for Coin { - type Output = Self; - - fn sub(self, rhs: Self) -> Self { - let denom = self.denom.clone(); - let lhs = self.to_minor(); - let rhs = rhs.to_minor(); - let lhs_amount = lhs.amount.parse::().unwrap(); - let rhs_amount = rhs.amount.parse::().unwrap(); - let amount = lhs_amount - rhs_amount; - let coin = Coin { - amount: amount.to_string(), - denom: Denom::Minor, - }; - match denom { - Denom::Major => coin.to_major(), - Denom::Minor => coin, - } - } -} - impl Coin { #[allow(unused)] pub fn major(amount: T) -> Coin { diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 91e3734cdf..9f78cef22b 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -77,6 +77,7 @@ fn main() { utils::owns_gateway, utils::owns_mixnode, utils::get_env, + utils::get_old_and_incorrect_hardcoded_fee, validator_api::status::gateway_core_node_status, validator_api::status::mixnode_core_node_status, validator_api::status::mixnode_inclusion_probability, diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index 405da42ee3..8bb1982d26 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -8,6 +8,7 @@ use mixnet_contract_common::Delegation; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; +use validator_client::nymd::{tx, CosmosCoin, Gas, GasPrice}; #[allow(non_snake_case)] #[cfg_attr(test, derive(ts_rs::TS))] @@ -65,6 +66,118 @@ pub async fn owns_gateway( .is_some()) } +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum Operation { + Upload, + Init, + Migrate, + ChangeAdmin, + Send, + + BondMixnode, + BondMixnodeOnBehalf, + UnbondMixnode, + UnbondMixnodeOnBehalf, + UpdateMixnodeConfig, + DelegateToMixnode, + DelegateToMixnodeOnBehalf, + UndelegateFromMixnode, + UndelegateFromMixnodeOnBehalf, + + BondGateway, + BondGatewayOnBehalf, + UnbondGateway, + UnbondGatewayOnBehalf, + + UpdateContractSettings, + + BeginMixnodeRewarding, + FinishMixnodeRewarding, + + TrackUnbondGateway, + TrackUnbondMixnode, + WithdrawVestedCoins, + TrackUndelegation, + CreatePeriodicVestingAccount, + + AdvanceCurrentInterval, + AdvanceCurrentEpoch, + WriteRewardedSet, + ClearRewardedSet, + UpdateMixnetAddress, + CheckpointMixnodes, + ReconcileDelegations, +} + +impl Operation { + fn default_gas_limit(&self) -> Gas { + match self { + Operation::Upload => 3_000_000u64.into(), + Operation::Init => 500_000u64.into(), + Operation::Migrate => 200_000u64.into(), + Operation::ChangeAdmin => 80_000u64.into(), + Operation::Send => 80_000u64.into(), + + Operation::BondMixnode => 175_000u64.into(), + Operation::BondMixnodeOnBehalf => 200_000u64.into(), + Operation::UnbondMixnode => 175_000u64.into(), + Operation::UnbondMixnodeOnBehalf => 175_000u64.into(), + Operation::UpdateMixnodeConfig => 175_000u64.into(), + Operation::DelegateToMixnode => 175_000u64.into(), + Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(), + Operation::UndelegateFromMixnode => 175_000u64.into(), + Operation::UndelegateFromMixnodeOnBehalf => 175_000u64.into(), + + Operation::BondGateway => 175_000u64.into(), + Operation::BondGatewayOnBehalf => 200_000u64.into(), + Operation::UnbondGateway => 175_000u64.into(), + Operation::UnbondGatewayOnBehalf => 200_000u64.into(), + + Operation::UpdateContractSettings => 175_000u64.into(), + Operation::BeginMixnodeRewarding => 175_000u64.into(), + Operation::FinishMixnodeRewarding => 175_000u64.into(), + Operation::TrackUnbondGateway => 175_000u64.into(), + Operation::TrackUnbondMixnode => 175_000u64.into(), + Operation::WithdrawVestedCoins => 175_000u64.into(), + Operation::TrackUndelegation => 175_000u64.into(), + Operation::CreatePeriodicVestingAccount => 175_000u64.into(), + Operation::AdvanceCurrentInterval => 175_000u64.into(), + Operation::WriteRewardedSet => 175_000u64.into(), + Operation::ClearRewardedSet => 175_000u64.into(), + Operation::UpdateMixnetAddress => 80_000u64.into(), + Operation::CheckpointMixnodes => 175_000u64.into(), + Operation::ReconcileDelegations => 500_000u64.into(), + Operation::AdvanceCurrentEpoch => 175_000u64.into(), + } + } + + fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> CosmosCoin { + gas_price * gas_limit + } + + fn determine_custom_fee(gas_price: &GasPrice, gas_limit: Gas) -> tx::Fee { + let fee = Self::calculate_fee(gas_price, gas_limit); + tx::Fee::from_amount_and_gas(fee, gas_limit) + } + + fn default_fee(&self, gas_price: &GasPrice) -> tx::Fee { + Self::determine_custom_fee(gas_price, self.default_gas_limit()) + } +} + +#[tauri::command] +pub async fn get_old_and_incorrect_hardcoded_fee( + state: tauri::State<'_, Arc>>, + operation: Operation, +) -> Result { + let mut approximate_fee = operation.default_fee(nymd_client!(state).gas_price()); + // on all our chains it should only ever contain a single type of currency + assert_eq!(approximate_fee.amount.len(), 1); + let coin: Coin = approximate_fee.amount.pop().unwrap().into(); + log::info!("hardcoded fee for {:?} is {:?}", operation, coin); + Ok(coin.to_major()) +} + #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/delegationresult.ts"))] #[derive(Serialize, Deserialize)] diff --git a/nym-wallet/src/requests/queries.ts b/nym-wallet/src/requests/queries.ts index cd3b03da46..aed26e023a 100644 --- a/nym-wallet/src/requests/queries.ts +++ b/nym-wallet/src/requests/queries.ts @@ -60,10 +60,10 @@ export const checkGatewayOwnership = async (): Promise => { // NOTE: this uses OUTDATED defaults that might have no resemblance with the reality // as for the actual transaction, the gas cost is being simulated beforehand export const getGasFee = async (operation: Operation): Promise => { - // THIS NO LONGER EXISTS : ) - // const res: Coin = await invoke('outdated_get_approximate_fee', { operation }); - // return res; - return new Promise(((resolve, reject) => reject())) + // current bandaid until `simulation` is properly implemented on the frontend. + // This essentially moves the usage of hardcoded values closer to the point of use. + const res: Coin = await invoke('get_old_and_incorrect_hardcoded_fee', { operation }); + return res; }; export const getInclusionProbability = async (identity: string): Promise => { From c513d5972490272c9455df597308fb2e665da987 Mon Sep 17 00:00:00 2001 From: tommy Date: Tue, 31 May 2022 17:01:02 +0100 Subject: [PATCH 10/24] allow dotenv not to be present --- validator-api/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 461d11ab51..c4750afad1 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -590,7 +590,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { async fn main() -> Result<()> { println!("Starting validator api..."); - dotenv::dotenv()?; + dotenv::dotenv(); cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { // instriment tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time From 7609e7084c7d834c8f04092ddc5a99010f891ff0 Mon Sep 17 00:00:00 2001 From: tommy Date: Tue, 31 May 2022 17:27:32 +0100 Subject: [PATCH 11/24] clippy warnings --- validator-api/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index c4750afad1..26cd59bd1c 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -590,7 +590,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { async fn main() -> Result<()> { println!("Starting validator api..."); - dotenv::dotenv(); + let _ = dotenv::dotenv(); cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { // instriment tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time From 41f40976286d5fb9e19b7d1d15ec5177627c43ba Mon Sep 17 00:00:00 2001 From: gala1234 Date: Wed, 1 Jun 2022 11:29:02 +0200 Subject: [PATCH 12/24] explorer: remove hardcoded part --- explorer/src/context/main.tsx | 6 ------ explorer/src/pages/Mixnodes/index.tsx | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/explorer/src/context/main.tsx b/explorer/src/context/main.tsx index d00d7fd1a0..4663cd2644 100644 --- a/explorer/src/context/main.tsx +++ b/explorer/src/context/main.tsx @@ -81,12 +81,6 @@ export const MainContextProvider: React.FC = ({ children }) => { setMixnodes((d) => ({ ...d, isLoading: true })); try { const data = status ? await Api.fetchMixnodesActiveSetByStatus(status) : await Api.fetchMixnodes(); - // TODO remove hard coded API response - data.map((item) => { - const node = item; - node.stake_saturation = 3.2; - return node; - }); setMixnodes({ data, isLoading: false }); } catch (error) { setMixnodes({ diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx index 54de0641af..7ced8703d9 100644 --- a/explorer/src/pages/Mixnodes/index.tsx +++ b/explorer/src/pages/Mixnodes/index.tsx @@ -258,9 +258,9 @@ export const PageMixnodes: React.FC = () => { renderCell: (params: GridRenderCellParams) => ( 100 ? theme.palette.warning.main : 'inherit', + ...getCellStyles(theme, params.row), }} component={RRDLink} to={`/network-components/mixnode/${params.row.identity_key}`} From b7d3333ff8c1379fc1cf6b4216743978c474656a Mon Sep 17 00:00:00 2001 From: tommy Date: Wed, 1 Jun 2022 11:19:54 +0100 Subject: [PATCH 13/24] Update to use new geo_locate stats --- explorer-api/README.md | 4 ++-- explorer-api/src/country_statistics/geolocate.rs | 2 +- explorer-api/src/main.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/explorer-api/README.md b/explorer-api/README.md index a60b941f7d..0555275f14 100644 --- a/explorer-api/README.md +++ b/explorer-api/README.md @@ -5,13 +5,13 @@ An API that provides data for the [Network Explorer](../explorer). Features: - - geolocates mixnodes using https://freegeoip.app/ + - geolocates mixnodes using https://app.ipbase.com/ - calculates how many nodes are in each country - proxies mixnode API requests to add HTTPS ## Running -Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://freegeoip.app/. +Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://app.ipbase.com/. Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt. diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index 170d4f5cc1..81e0944dc0 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -129,7 +129,7 @@ enum LocateError { async fn locate(ip: &str) -> Result { let api_key = ::std::env::var("GEO_IP_SERVICE_API_KEY") .expect("Env var GEO_IP_SERVICE_API_KEY is not set"); - let uri = format!("{}/{}?apikey={}", crate::GEO_IP_SERVICE, ip, api_key); + let uri = format!("{}/?apikey={}&ip={}", crate::GEO_IP_SERVICE, api_key, ip); match reqwest::get(uri.clone()).await { Ok(response) => { if response.status() == 429 { diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index e46a0cbeae..ed96dd4fff 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -18,7 +18,7 @@ mod state; mod tasks; mod validators; -const GEO_IP_SERVICE: &str = "https://api.freegeoip.app/json"; +const GEO_IP_SERVICE: &str = "https://api.ipbase.com/json"; const COUNTRY_DATA_REFRESH_INTERVAL: u64 = 60 * 15; // every 15 minutes #[tokio::main] From c79ee5052f0fd8326318f6396e9198078c473cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 1 Jun 2022 13:02:16 +0200 Subject: [PATCH 14/24] validator-api: add detailed mixnode bond endpoints (#1294) * validator-api: add mixnodes-detailed endpoint * validator-api: add detailed variants of active and rewarded set * explorer-api: cache as mixnode bond detailed * explorer-api: add in stake-saturation in response * changelog: update * rustfmt * validator-api: rename to MixNodeBondResponse * validator-api: cache MixNodeBondResponse instead * validator-api: rename to MixNodeBondAnnotated * validator-client: fix unused warning * explorer-api: remove unnecessary clone * rustfmt --- CHANGELOG.md | 1 + Cargo.lock | 1 + .../validator-client/src/client.rs | 22 +++- .../validator-client/src/validator_api/mod.rs | 44 ++++++- .../src/validator_api/routes.rs | 1 + .../src/country_statistics/geolocate.rs | 16 +-- explorer-api/src/mix_node/http.rs | 16 ++- explorer-api/src/mix_node/models.rs | 1 + explorer-api/src/mix_nodes/models.rs | 39 ++++--- explorer-api/src/ping/http.rs | 2 +- explorer-api/src/state.rs | 4 +- explorer-api/src/tasks.rs | 24 ++-- validator-api/Cargo.toml | 1 + validator-api/src/contract_cache/mod.rs | 108 ++++++++++++++---- validator-api/src/contract_cache/routes.rs | 29 ++++- validator-api/src/node_status_api/routes.rs | 6 +- validator-api/src/rewarded_set_updater/mod.rs | 16 ++- .../validator-api-requests/src/models.rs | 18 ++- 18 files changed, 261 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8334cd702..6548a6200c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- validator-api: add detailed mixnode bond endpoints, and explorer-api makes use of that data to append stake saturation. - wallet: require password to switch accounts - wallet: add simple CLI tool for decrypting and recovering the wallet file. - wallet: added support for multiple accounts ([#1265]) diff --git a/Cargo.lock b/Cargo.lock index 611108642d..f2c1cbf381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3213,6 +3213,7 @@ dependencies = [ "coconut-interface", "config", "console-subscriber", + "cosmwasm-std", "credential-storage", "credentials", "crypto", diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index adcf4dd2f9..f3f4842013 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -6,12 +6,12 @@ use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, Verifica use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; use url::Url; -#[cfg(feature = "nymd-client")] -use validator_api_requests::models::UptimeResponse; use validator_api_requests::models::{ CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, }; +#[cfg(feature = "nymd-client")] +use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse}; #[cfg(feature = "nymd-client")] use network_defaults::DEFAULT_NETWORK; @@ -233,18 +233,36 @@ impl Client { Ok(self.validator_api.get_mixnodes().await?) } + pub async fn get_cached_mixnodes_detailed( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_mixnodes_detailed().await?) + } + pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { Ok(self.validator_api.get_rewarded_mixnodes().await?) } + pub async fn get_cached_rewarded_mixnodes_detailed( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?) + } + pub async fn get_cached_active_mixnodes( &self, ) -> Result, ValidatorClientError> { Ok(self.validator_api.get_active_mixnodes().await?) } + pub async fn get_cached_active_mixnodes_detailed( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_active_mixnodes_detailed().await?) + } + pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { Ok(self.validator_api.get_gateways().await?) } diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 4d5463ab3a..66c86f55b6 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use url::Url; use validator_api_requests::models::{ - CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse, - RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, + CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated, + MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, }; pub mod error; @@ -85,6 +85,16 @@ impl Client { .await } + pub async fn get_mixnodes_detailed( + &self, + ) -> Result, ValidatorAPIError> { + self.query_validator_api( + &[routes::API_VERSION, routes::MIXNODES, routes::DETAILED], + NO_PARAMS, + ) + .await + } + pub async fn get_gateways(&self) -> Result, ValidatorAPIError> { self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) .await @@ -98,6 +108,21 @@ impl Client { .await } + pub async fn get_active_mixnodes_detailed( + &self, + ) -> Result, ValidatorAPIError> { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::MIXNODES, + routes::ACTIVE, + routes::DETAILED, + ], + NO_PARAMS, + ) + .await + } + pub async fn get_rewarded_mixnodes(&self) -> Result, ValidatorAPIError> { self.query_validator_api( &[routes::API_VERSION, routes::MIXNODES, routes::REWARDED], @@ -106,6 +131,21 @@ impl Client { .await } + pub async fn get_rewarded_mixnodes_detailed( + &self, + ) -> Result, ValidatorAPIError> { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::MIXNODES, + routes::REWARDED, + routes::DETAILED, + ], + NO_PARAMS, + ) + .await + } + pub async fn get_probs_mixnode_rewarded( &self, mixnode_id: &str, diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index d305e0869b..61c4e309e8 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -7,6 +7,7 @@ pub const API_VERSION: &str = VALIDATOR_API_VERSION; pub const MIXNODES: &str = "mixnodes"; pub const GATEWAYS: &str = "gateways"; +pub const DETAILED: &str = "detailed"; pub const ACTIVE: &str = "active"; pub const REWARDED: &str = "rewarded"; diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index 81e0944dc0..41b7eb6f3a 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -51,7 +51,7 @@ impl GeoLocateTask { .state .inner .mixnodes - .is_location_valid(&cache_item.mix_node.identity_key) + .is_location_valid(&cache_item.mix_node().identity_key) .await { // when the cached location is valid, don't locate and continue to next mix node @@ -59,14 +59,14 @@ impl GeoLocateTask { } // the mix node has not been located or is the cache time has expired - match locate(&cache_item.mix_node.host).await { + match locate(&cache_item.mix_node().host).await { Ok(geo_location) => { let location = Location::new(geo_location); trace!( "{} mix nodes already located. Ip {} is located in {:#?}", i, - cache_item.mix_node.host, + cache_item.mix_node().host, location.three_letter_iso_country_code, ); @@ -80,7 +80,7 @@ impl GeoLocateTask { self.state .inner .mixnodes - .set_location(&cache_item.mix_node.identity_key, Some(location)) + .set_location(&cache_item.mix_node().identity_key, Some(location)) .await; // one node has been located, so return out of the loop @@ -89,22 +89,22 @@ impl GeoLocateTask { Err(e) => match e { LocateError::ReqwestError(e) => warn!( "❌ Oh no! Location for {} failed {}", - cache_item.mix_node.host, e + cache_item.mix_node().host, e ), LocateError::NotFound(e) => { warn!( "❌ Location for {} not found. Response body: {}", - cache_item.mix_node.host, e + cache_item.mix_node().host, e ); self.state .inner .mixnodes - .set_location(&cache_item.mix_node.identity_key, None) + .set_location(&cache_item.mix_node().identity_key, None) .await; }, LocateError::RateLimited(e) => warn!( "❌ Oh no, we've been rate limited! Location for {} failed. Response body: {}", - cache_item.mix_node.host, e + cache_item.mix_node().host, e ), }, } diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 2b1946dc83..884deb698e 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -70,8 +70,8 @@ pub(crate) async fn get_description( match state.inner.get_mix_node(pubkey).await { Some(bond) => { match get_mix_node_description( - &bond.mix_node.host, - &bond.mix_node.http_api_port, + &bond.mix_node().host, + &bond.mix_node().http_api_port, ) .await { @@ -87,7 +87,10 @@ pub(crate) async fn get_description( Err(e) => { error!( "Unable to get description for {} on {}:{} -> {}", - pubkey, bond.mix_node.host, bond.mix_node.http_api_port, e + pubkey, + bond.mix_node().host, + bond.mix_node().http_api_port, + e ); Option::None } @@ -114,7 +117,7 @@ pub(crate) async fn get_stats( trace!("No valid cache value for {}", pubkey); match state.inner.get_mix_node(pubkey).await { Some(bond) => { - match get_mix_node_stats(&bond.mix_node.host, &bond.mix_node.http_api_port) + match get_mix_node_stats(&bond.mix_node().host, &bond.mix_node().http_api_port) .await { Ok(response) => { @@ -129,7 +132,10 @@ pub(crate) async fn get_stats( Err(e) => { error!( "Unable to get description for {} on {}:{} -> {}", - pubkey, bond.mix_node.host, bond.mix_node.http_api_port, e + pubkey, + bond.mix_node().host, + bond.mix_node().http_api_port, + e ); Option::None } diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index c038773c71..9fe0bfaf40 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -29,6 +29,7 @@ pub(crate) struct PrettyDetailedMixNodeBond { pub layer: Layer, pub mix_node: MixNode, pub avg_uptime: Option, + pub stake_saturation: f32, } pub(crate) struct MixNodeCache { diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index fb7f57a174..8db58fba0f 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -8,8 +8,7 @@ use std::time::{Duration, SystemTime}; use serde::Serialize; use tokio::sync::RwLock; -use mixnet_contract_common::MixNodeBond; -use validator_client::models::UptimeResponse; +use validator_client::models::{MixNodeBondAnnotated, UptimeResponse}; use crate::cache::Cache; use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; @@ -32,7 +31,7 @@ pub(crate) struct MixNodeSummary { #[derive(Clone, Debug)] pub(crate) struct MixNodesResult { pub(crate) valid_until: SystemTime, - pub(crate) all_mixnodes: HashMap, + pub(crate) all_mixnodes: HashMap, active_mixnodes: HashSet, rewarded_mixnodes: HashSet, } @@ -61,7 +60,7 @@ impl MixNodesResult { self.valid_until >= SystemTime::now() } - fn get_mixnode(&self, pubkey: &str) -> Option { + fn get_mixnode(&self, pubkey: &str) -> Option { if self.is_valid() { self.all_mixnodes.get(pubkey).cloned() } else { @@ -69,7 +68,7 @@ impl MixNodesResult { } } - fn get_mixnodes(&self) -> Option> { + fn get_mixnodes(&self) -> Option> { if self.is_valid() { Some(self.all_mixnodes.clone()) } else { @@ -128,11 +127,11 @@ impl ThreadsafeMixNodesCache { ); } - pub(crate) async fn get_mixnode(&self, pubkey: &str) -> Option { + pub(crate) async fn get_mixnode(&self, pubkey: &str) -> Option { self.mixnodes.read().await.get_mixnode(pubkey) } - pub(crate) async fn get_mixnodes(&self) -> Option> { + pub(crate) async fn get_mixnodes(&self) -> Option> { self.mixnodes.read().await.get_mixnodes() } @@ -151,13 +150,14 @@ impl ThreadsafeMixNodesCache { match bond { Some(bond) => Some(PrettyDetailedMixNodeBond { location: location.and_then(|l| l.location.clone()), - status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key), - pledge_amount: bond.pledge_amount, - total_delegation: bond.total_delegation, - owner: bond.owner, - layer: bond.layer, - mix_node: bond.mix_node, + status: mixnodes_guard.determine_node_status(&bond.mix_node().identity_key), + pledge_amount: bond.mixnode_bond.pledge_amount, + total_delegation: bond.mixnode_bond.total_delegation, + owner: bond.mixnode_bond.owner, + layer: bond.mixnode_bond.layer, + mix_node: bond.mixnode_bond.mix_node, avg_uptime: health.map(|m| m.avg_uptime), + stake_saturation: bond.stake_saturation, }), None => None, } @@ -172,18 +172,19 @@ impl ThreadsafeMixNodesCache { .all_mixnodes .values() .map(|bond| { - let location = location_guard.get(&bond.mix_node.identity_key); - let copy = bond.clone(); - let health = mixnode_health_guard.get(&bond.mix_node.identity_key); + let location = location_guard.get(&bond.mix_node().identity_key); + let copy = bond.mixnode_bond.clone(); + let health = mixnode_health_guard.get(&bond.mix_node().identity_key); PrettyDetailedMixNodeBond { location: location.and_then(|l| l.location.clone()), - status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key), + status: mixnodes_guard.determine_node_status(&bond.mix_node().identity_key), pledge_amount: copy.pledge_amount, total_delegation: copy.total_delegation, owner: copy.owner, layer: copy.layer, mix_node: copy.mix_node, avg_uptime: health.map(|m| m.avg_uptime), + stake_saturation: bond.stake_saturation, } }) .collect() @@ -191,14 +192,14 @@ impl ThreadsafeMixNodesCache { pub(crate) async fn update_cache( &self, - all_bonds: Vec, + all_bonds: Vec, rewarded_nodes: HashSet, active_nodes: HashSet, ) { let mut guard = self.mixnodes.write().await; guard.all_mixnodes = all_bonds .into_iter() - .map(|bond| (bond.mix_node.identity_key.to_string(), bond)) + .map(|bond| (bond.mix_node().identity_key.to_string(), bond)) .collect(); guard.rewarded_mixnodes = rewarded_nodes; guard.active_mixnodes = active_nodes; diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 379084436e..7f451529e3 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -42,7 +42,7 @@ pub(crate) async fn index( state.inner.ping.set_pending(pubkey).await; // do the check - let ports = Some(port_check(&bond).await); + let ports = Some(port_check(&bond.mixnode_bond).await); trace!("Tested mix node {}: {:?}", pubkey, ports); let response = PingResponse { ports, diff --git a/explorer-api/src/state.rs b/explorer-api/src/state.rs index 260528def7..1d5a569021 100644 --- a/explorer-api/src/state.rs +++ b/explorer-api/src/state.rs @@ -6,7 +6,7 @@ use log::info; use serde::{Deserialize, Serialize}; use crate::client::ThreadsafeValidatorClient; -use mixnet_contract_common::MixNodeBond; +use validator_client::models::MixNodeBondAnnotated; use crate::country_statistics::country_nodes_distribution::{ CountryNodesDistribution, ThreadsafeCountryNodesDistribution, @@ -35,7 +35,7 @@ pub struct ExplorerApiState { } impl ExplorerApiState { - pub(crate) async fn get_mix_node(&self, pubkey: &str) -> Option { + pub(crate) async fn get_mix_node(&self, pubkey: &str) -> Option { self.mixnodes.get_mixnode(pubkey).await } } diff --git a/explorer-api/src/tasks.rs b/explorer-api/src/tasks.rs index e89ca16c62..575e48f314 100644 --- a/explorer-api/src/tasks.rs +++ b/explorer-api/src/tasks.rs @@ -3,8 +3,8 @@ use std::future::Future; -use mixnet_contract_common::{GatewayBond, MixNodeBond}; -use validator_client::models::UptimeResponse; +use mixnet_contract_common::GatewayBond; +use validator_client::models::{MixNodeBondAnnotated, UptimeResponse}; use validator_client::nymd::error::NymdError; use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse}; use validator_client::ValidatorClientError; @@ -22,10 +22,10 @@ impl ExplorerApiTasks { } // a helper to remove duplicate code when grabbing active/rewarded/all mixnodes - async fn retrieve_mixnodes<'a, F, Fut>(&'a self, f: F) -> Vec + async fn retrieve_mixnodes<'a, F, Fut>(&'a self, f: F) -> Vec where F: FnOnce(&'a validator_client::Client) -> Fut, - Fut: Future, ValidatorClientError>>, + Fut: Future, ValidatorClientError>>, { let bonds = match f(&self.state.inner.validator_client.0).await { Ok(result) => result, @@ -39,9 +39,9 @@ impl ExplorerApiTasks { bonds } - async fn retrieve_all_mixnodes(&self) -> Vec { + async fn retrieve_all_mixnodes(&self) -> Vec { info!("About to retrieve all mixnode bonds..."); - self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes) + self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed) .await } @@ -77,15 +77,15 @@ impl ExplorerApiTasks { Ok(response) } - async fn retrieve_rewarded_mixnodes(&self) -> Vec { + async fn retrieve_rewarded_mixnodes(&self) -> Vec { info!("About to retrieve rewarded mixnode bonds..."); - self.retrieve_mixnodes(validator_client::Client::get_cached_rewarded_mixnodes) + self.retrieve_mixnodes(validator_client::Client::get_cached_rewarded_mixnodes_detailed) .await } - async fn retrieve_active_mixnodes(&self) -> Vec { + async fn retrieve_active_mixnodes(&self) -> Vec { info!("About to retrieve active mixnode bonds..."); - self.retrieve_mixnodes(validator_client::Client::get_cached_active_mixnodes) + self.retrieve_mixnodes(validator_client::Client::get_cached_active_mixnodes_detailed) .await } @@ -106,13 +106,13 @@ impl ExplorerApiTasks { .retrieve_rewarded_mixnodes() .await .into_iter() - .map(|bond| bond.mix_node.identity_key) + .map(|bond| bond.mix_node().identity_key.clone()) .collect(); let active_nodes = self .retrieve_active_mixnodes() .await .into_iter() - .map(|bond| bond.mix_node.identity_key) + .map(|bond| bond.mix_node().identity_key.clone()) .collect(); self.state .inner diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 11f5eb275b..ed31e57d8d 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -50,6 +50,7 @@ schemars = { version = "0.8", features = ["preserve_order"] } ## internal coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } config = { path = "../common/config" } +cosmwasm-std = "1.0.0-beta8" crypto = { path="../common/crypto" } gateway-client = { path="../common/client-libs/gateway-client" } mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" } diff --git a/validator-api/src/contract_cache/mod.rs b/validator-api/src/contract_cache/mod.rs index 4e51d12400..3ff29e40ce 100644 --- a/validator-api/src/contract_cache/mod.rs +++ b/validator-api/src/contract_cache/mod.rs @@ -22,7 +22,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; use tokio::time; -use validator_api_requests::models::MixnodeStatus; +use validator_api_requests::models::{MixNodeBondAnnotated, MixnodeStatus}; use validator_client::nymd::CosmWasmClient; pub(crate) mod routes; @@ -40,14 +40,14 @@ pub struct ValidatorCache { } struct ValidatorCacheInner { - mixnodes: Cache>, + mixnodes: Cache>, gateways: Cache>, mixnodes_blacklist: Cache>, gateways_blacklist: Cache>, - rewarded_set: Cache>, - active_set: Cache>, + rewarded_set: Cache>, + active_set: Cache>, current_reward_params: Cache, current_epoch: Cache>, @@ -99,11 +99,32 @@ impl ValidatorCacheRefresher { } } + fn annotate_bond_with_details( + mixnodes: Vec, + interval_reward_params: EpochRewardParams, + ) -> Vec { + mixnodes + .into_iter() + .map(|mixnode_bond| { + let stake_saturation = mixnode_bond + .stake_saturation( + interval_reward_params.circulating_supply(), + interval_reward_params.rewarded_set_size() as u32, + ) + .to_num(); + MixNodeBondAnnotated { + mixnode_bond, + stake_saturation, + } + }) + .collect() + } + fn collect_rewarded_and_active_set_details( &self, - all_mixnodes: &[MixNodeBond], + all_mixnodes: &[MixNodeBondAnnotated], rewarded_set_identities: Vec<(IdentityKey, RewardedSetNodeStatus)>, - ) -> (Vec, Vec) { + ) -> (Vec, Vec) { let mut active_set = Vec::new(); let mut rewarded_set = Vec::new(); let rewarded_set_identities = rewarded_set_identities @@ -111,7 +132,7 @@ impl ValidatorCacheRefresher { .collect::>(); for mix in all_mixnodes { - if let Some(status) = rewarded_set_identities.get(mix.identity()) { + if let Some(status) = rewarded_set_identities.get(mix.mixnode_bond.identity()) { rewarded_set.push(mix.clone()); if status.is_active() { active_set.push(mix.clone()) @@ -126,11 +147,16 @@ impl ValidatorCacheRefresher { where C: CosmWasmClient + Sync, { + let epoch_rewarding_params = self.nymd_client.get_current_epoch_reward_params().await?; + let current_epoch = self.nymd_client.get_current_epoch().await?; + let (mixnodes, gateways) = tokio::try_join!( self.nymd_client.get_mixnodes(), self.nymd_client.get_gateways(), )?; + let mixnodes = Self::annotate_bond_with_details(mixnodes, epoch_rewarding_params); + let (rewarded_set, active_set) = if let Ok(rewarded_set_identities) = self.nymd_client.get_rewarded_set_identities().await { @@ -139,9 +165,6 @@ impl ValidatorCacheRefresher { (Vec::new(), Vec::new()) }; - let epoch_rewarding_params = self.nymd_client.get_current_epoch_reward_params().await?; - let current_epoch = self.nymd_client.get_current_epoch().await?; - info!( "Updating validator cache. There are {} mixnodes and {} gateways", mixnodes.len(), @@ -184,9 +207,12 @@ impl ValidatorCacheRefresher { pub(crate) fn validator_cache_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![ settings: routes::get_mixnodes, + routes::get_mixnodes_detailed, routes::get_gateways, routes::get_active_set, + routes::get_active_set_detailed, routes::get_rewarded_set, + routes::get_rewarded_set_detailed, routes::get_blacklisted_mixnodes, routes::get_blacklisted_gateways, routes::get_epoch_reward_params, @@ -210,10 +236,10 @@ impl ValidatorCache { async fn update_cache( &self, - mixnodes: Vec, + mixnodes: Vec, gateways: Vec, - rewarded_set: Vec, - active_set: Vec, + rewarded_set: Vec, + active_set: Vec, epoch_rewarding_params: EpochRewardParams, current_epoch: Interval, ) { @@ -312,7 +338,7 @@ impl ValidatorCache { error!("Failed to update gateways blacklist"); } - pub async fn mixnodes(&self) -> Vec { + pub async fn mixnodes_detailed(&self) -> Vec { let blacklist = self.mixnodes_blacklist().await; let mixnodes = match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.mixnodes.clone(), @@ -326,7 +352,7 @@ impl ValidatorCache { mixnodes .value .iter() - .filter(|mix| !blacklist.value.contains(mix.identity())) + .filter(|mix| !blacklist.value.contains(mix.mixnode_bond.identity())) .cloned() .collect() } else { @@ -334,9 +360,23 @@ impl ValidatorCache { } } + pub async fn mixnodes(&self) -> Vec { + self.mixnodes_detailed() + .await + .into_iter() + .map(|bond| bond.mixnode_bond) + .collect() + } + pub async fn mixnodes_all(&self) -> Vec { match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => cache.mixnodes.clone().into_inner(), + Ok(cache) => cache + .mixnodes + .clone() + .into_inner() + .into_iter() + .map(|bond| bond.mixnode_bond) + .collect(), Err(e) => { error!("{}", e); Vec::new() @@ -376,7 +416,7 @@ impl ValidatorCache { } } - pub async fn rewarded_set(&self) -> Cache> { + pub async fn rewarded_set_detailed(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.rewarded_set.clone(), Err(e) => { @@ -386,7 +426,16 @@ impl ValidatorCache { } } - pub async fn active_set(&self) -> Cache> { + pub async fn rewarded_set(&self) -> Vec { + self.rewarded_set_detailed() + .await + .value + .into_iter() + .map(|bond| bond.mixnode_bond) + .collect() + } + + pub async fn active_set_detailed(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.active_set.clone(), Err(e) => { @@ -396,6 +445,15 @@ impl ValidatorCache { } } + pub async fn active_set(&self) -> Vec { + self.active_set_detailed() + .await + .value + .into_iter() + .map(|bond| bond.mixnode_bond) + .collect() + } + pub(crate) async fn epoch_reward_params(&self) -> Cache { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.current_reward_params.clone(), @@ -419,30 +477,30 @@ impl ValidatorCache { pub async fn mixnode_details( &self, identity: IdentityKeyRef<'_>, - ) -> (Option, MixnodeStatus) { + ) -> (Option, MixnodeStatus) { // it might not be the most optimal to possibly iterate the entire vector to find (or not) // the relevant value. However, the vectors are relatively small (< 10_000 elements, < 1000 for active set) - let active_set = &self.active_set().await.value; + let active_set = &self.active_set_detailed().await.value; if let Some(bond) = active_set .iter() - .find(|mix| mix.mix_node.identity_key == identity) + .find(|mix| mix.mix_node().identity_key == identity) { return (Some(bond.clone()), MixnodeStatus::Active); } - let rewarded_set = &self.rewarded_set().await.value; + let rewarded_set = &self.rewarded_set_detailed().await.value; if let Some(bond) = rewarded_set .iter() - .find(|mix| mix.mix_node.identity_key == identity) + .find(|mix| mix.mix_node().identity_key == identity) { return (Some(bond.clone()), MixnodeStatus::Standby); } - let all_bonded = &self.mixnodes().await; + let all_bonded = &self.mixnodes_detailed().await; if let Some(bond) = all_bonded .iter() - .find(|mix| mix.mix_node.identity_key == identity) + .find(|mix| mix.mix_node().identity_key == identity) { (Some(bond.clone()), MixnodeStatus::Inactive) } else { diff --git a/validator-api/src/contract_cache/routes.rs b/validator-api/src/contract_cache/routes.rs index c2d0f075db..2621b7e894 100644 --- a/validator-api/src/contract_cache/routes.rs +++ b/validator-api/src/contract_cache/routes.rs @@ -8,6 +8,7 @@ use rocket::serde::json::Json; use rocket::State; use rocket_okapi::openapi; use std::collections::HashSet; +use validator_api_requests::models::MixNodeBondAnnotated; #[openapi(tag = "contract-cache")] #[get("/mixnodes")] @@ -15,6 +16,14 @@ pub async fn get_mixnodes(cache: &State) -> Json, +) -> Json> { + Json(cache.mixnodes_detailed().await) +} + #[openapi(tag = "contract-cache")] #[get("/gateways")] pub async fn get_gateways(cache: &State) -> Json> { @@ -24,13 +33,29 @@ pub async fn get_gateways(cache: &State) -> Json) -> Json> { - Json(cache.rewarded_set().await.value) + Json(cache.rewarded_set().await) +} + +#[openapi(tag = "contract-cache")] +#[get("/mixnodes/rewarded/detailed")] +pub async fn get_rewarded_set_detailed( + cache: &State, +) -> Json> { + Json(cache.rewarded_set_detailed().await.value) } #[openapi(tag = "contract-cache")] #[get("/mixnodes/active")] pub async fn get_active_set(cache: &State) -> Json> { - Json(cache.active_set().await.value) + Json(cache.active_set().await) +} + +#[openapi(tag = "contract-cache")] +#[get("/mixnodes/active/detailed")] +pub async fn get_active_set_detailed( + cache: &State, +) -> Json> { + Json(cache.active_set_detailed().await.value) } #[openapi(tag = "contract-cache")] diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index a03f59661d..22ccbee404 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -147,7 +147,7 @@ pub(crate) async fn get_mixnode_reward_estimation( let node_reward_params = NodeRewardParams::new(0, uptime.u8() as u128, status.is_active()); let reward_params = RewardParams::new(reward_params, node_reward_params); - match bond.estimate_reward(&reward_params) { + match bond.mixnode_bond.estimate_reward(&reward_params) { Ok(reward_estimate) => { let reponse = RewardEstimationResponse { estimated_total_node_reward: reward_estimate.total_node_reward, @@ -181,11 +181,13 @@ pub(crate) async fn get_mixnode_stake_saturation( ) -> Result, ErrorResponse> { let (bond, _) = cache.mixnode_details(&identity).await; if let Some(bond) = bond { + // Recompute the stake saturation just so that we can confidentaly state that the `as_at` + // field is consistent and correct. Luckily this is very cheap. let interval_reward_params = cache.epoch_reward_params().await; let as_at = interval_reward_params.timestamp(); let interval_reward_params = interval_reward_params.into_inner(); - let saturation = bond.stake_saturation( + let saturation = bond.mixnode_bond.stake_saturation( interval_reward_params.circulating_supply(), interval_reward_params.rewarded_set_size() as u32, ); diff --git a/validator-api/src/rewarded_set_updater/mod.rs b/validator-api/src/rewarded_set_updater/mod.rs index 9f008348d0..d6006d2617 100644 --- a/validator-api/src/rewarded_set_updater/mod.rs +++ b/validator-api/src/rewarded_set_updater/mod.rs @@ -161,21 +161,25 @@ impl RewardedSetUpdater { let epoch = self.epoch().await?; let active_set = self .validator_cache - .active_set() + .active_set_detailed() .await .into_inner() .into_iter() - .map(|bond| bond.mix_node.identity_key) + .map(|bond| bond.mix_node().identity_key.clone()) .collect::>(); - let rewarded_set = self.validator_cache.rewarded_set().await.into_inner(); + let rewarded_set = self + .validator_cache + .rewarded_set_detailed() + .await + .into_inner(); let mut eligible_nodes = Vec::with_capacity(rewarded_set.len()); for rewarded_node in rewarded_set.into_iter() { let uptime = self .storage .get_average_mixnode_uptime_in_the_last_24hrs( - rewarded_node.identity(), + rewarded_node.mixnode_bond.identity(), epoch.end_unix_timestamp(), ) .await?; @@ -183,11 +187,11 @@ impl RewardedSetUpdater { let node_reward_params = NodeRewardParams::new( 0, uptime.u8().into(), - active_set.contains(rewarded_node.identity()), + active_set.contains(rewarded_node.mixnode_bond.identity()), ); eligible_nodes.push(MixnodeToReward { - identity: rewarded_node.identity().clone(), + identity: rewarded_node.mixnode_bond.identity().clone(), params: node_reward_params, }) } diff --git a/validator-api/validator-api-requests/src/models.rs b/validator-api/validator-api-requests/src/models.rs index d98b5f798d..5998134d9b 100644 --- a/validator-api/validator-api-requests/src/models.rs +++ b/validator-api/validator-api-requests/src/models.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use mixnet_contract_common::reward_params::RewardParams; +use mixnet_contract_common::{reward_params::RewardParams, MixNode, MixNodeBond}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt; @@ -53,6 +53,18 @@ pub struct MixnodeStatusResponse { pub status: MixnodeStatus, } +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct MixNodeBondAnnotated { + pub mixnode_bond: MixNodeBond, + pub stake_saturation: StakeSaturation, +} + +impl MixNodeBondAnnotated { + pub fn mix_node(&self) -> &MixNode { + &self.mixnode_bond.mix_node + } +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] pub struct RewardEstimationResponse { pub estimated_total_node_reward: u64, @@ -81,10 +93,12 @@ pub struct UptimeResponse { ) )] pub struct StakeSaturationResponse { - pub saturation: f32, + pub saturation: StakeSaturation, pub as_at: i64, } +pub type StakeSaturation = f32; + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr( From 802334b37e7e9c953a057e16325796d5fd72511e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 1 Jun 2022 13:36:34 +0200 Subject: [PATCH 15/24] explorer-api: add endpoint for summed delegations (#1299) * explorer-api: add endpoint for summed delegations * changelog: add note --- CHANGELOG.md | 1 + Cargo.lock | 1 + explorer-api/Cargo.toml | 19 +++---- explorer-api/src/mix_node/delegations.rs | 19 +++++++ explorer-api/src/mix_node/http.rs | 16 +++++- explorer-api/src/mix_node/models.rs | 65 ++++++++++++++++++++++++ 6 files changed, 110 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6548a6200c..8fa72933a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- explorer-api: learned how to sum the delegations by owner in a new endpoint. - validator-api: add detailed mixnode bond endpoints, and explorer-api makes use of that data to append stake saturation. - wallet: require password to switch accounts - wallet: add simple CLI tool for decrypting and recovering the wallet file. diff --git a/Cargo.lock b/Cargo.lock index f2c1cbf381..ccc731321e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1574,6 +1574,7 @@ dependencies = [ "chrono", "humantime-serde", "isocountry", + "itertools", "log", "mixnet-contract-common", "network-defaults", diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index cef399d314..a4787ecebc 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -6,21 +6,22 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = { version = "0.4.19", features = ["serde"] } +humantime-serde = "1.0" isocountry = "0.3.2" +itertools = "0.10.3" +log = "0.4.0" +okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } +pretty_env_logger = "0.4.0" reqwest = "0.11.4" rocket = {version = "0.5.0-rc.1", features=["json"] } rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } -serde = "1.0.126" -humantime-serde = "1.0" -serde_json = "1.0.66" -tokio = {version = "1.9.0", features = ["full"] } -chrono = { version = "0.4.19", features = ["serde"] } -schemars = { version = "0.8", features = ["preserve_order"] } -okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] } -log = "0.4.0" -pretty_env_logger = "0.4.0" +schemars = { version = "0.8", features = ["preserve_order"] } +serde = "1.0.126" +serde_json = "1.0.66" thiserror = "1.0.29" +tokio = {version = "1.9.0", features = ["full"] } mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } network-defaults = { path = "../common/network-defaults" } diff --git a/explorer-api/src/mix_node/delegations.rs b/explorer-api/src/mix_node/delegations.rs index 0b1a7dd563..95b7d17d2a 100644 --- a/explorer-api/src/mix_node/delegations.rs +++ b/explorer-api/src/mix_node/delegations.rs @@ -1,9 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use itertools::Itertools; + use crate::client::ThreadsafeValidatorClient; use mixnet_contract_common::Delegation; +use super::models::SummedDelegations; + pub(crate) async fn get_single_mixnode_delegations( client: &ThreadsafeValidatorClient, pubkey: &str, @@ -21,3 +25,18 @@ pub(crate) async fn get_single_mixnode_delegations( }; delegates } + +pub(crate) async fn get_single_mixnode_delegations_summed( + client: &ThreadsafeValidatorClient, + pubkey: &str, +) -> Vec { + let delegations_by_owner = get_single_mixnode_delegations(client, pubkey) + .await + .into_iter() + .into_group_map_by(|delegation| delegation.owner.clone()); + + delegations_by_owner + .iter() + .filter_map(|(_, delegations)| SummedDelegations::from(delegations)) + .collect() +} diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 884deb698e..e3450a0d24 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -11,16 +11,19 @@ use rocket_okapi::settings::OpenApiSettings; use mixnet_contract_common::Delegation; -use crate::mix_node::delegations::get_single_mixnode_delegations; +use crate::mix_node::delegations::{ + get_single_mixnode_delegations, get_single_mixnode_delegations_summed, +}; use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats; use crate::mix_node::models::{ - EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, + EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, SummedDelegations, }; use crate::state::ExplorerApiStateContext; pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![ settings: get_delegations, + get_delegations_summed, get_by_id, get_description, get_stats, @@ -54,6 +57,15 @@ pub(crate) async fn get_delegations( Json(get_single_mixnode_delegations(&state.inner.validator_client, pubkey).await) } +#[openapi(tag = "mix_node")] +#[get("//delegations/summed")] +pub(crate) async fn get_delegations_summed( + pubkey: &str, + state: &State, +) -> Json> { + Json(get_single_mixnode_delegations_summed(&state.inner.validator_client, pubkey).await) +} + #[openapi(tag = "mix_node")] #[get("//description")] pub(crate) async fn get_description( diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 9fe0bfaf40..f5b9ebccb1 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -4,6 +4,7 @@ use crate::cache::Cache; use crate::mix_nodes::location::Location; use mixnet_contract_common::{Addr, Coin, Layer, MixNode}; +use mixnet_contract_common::{Delegation, IdentityKey}; use serde::Deserialize; use serde::Serialize; use std::sync::Arc; @@ -32,6 +33,34 @@ pub(crate) struct PrettyDetailedMixNodeBond { pub stake_saturation: f32, } +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] +pub struct SummedDelegations { + pub owner: Addr, + pub node_identity: IdentityKey, + pub amount: Coin, +} + +impl SummedDelegations { + pub fn from(delegations: &[Delegation]) -> Option { + let owner = get_common_owner(delegations)?; + let node_identity = get_common_node_identity(delegations)?; + let denom = get_common_denom(delegations)?; + + let sum = delegations + .iter() + .map(|delegation| delegation.amount.amount) + .sum(); + + let amount = Coin { denom, amount: sum }; + + Some(SummedDelegations { + owner, + node_identity, + amount, + }) + } +} + pub(crate) struct MixNodeCache { pub(crate) descriptions: Cache, pub(crate) node_stats: Cache, @@ -138,3 +167,39 @@ pub(crate) struct EconomicDynamicsStats { pub(crate) current_interval_uptime: u8, } + +fn get_common_owner(delegations: &[Delegation]) -> Option { + let owner = delegations.iter().next()?.owner(); + if delegations + .iter() + .any(|delegation| delegation.owner() != owner) + { + log::warn!("Unexpected different owners when summing delegations"); + return None; + } + Some(owner) +} + +fn get_common_node_identity(delegations: &[Delegation]) -> Option { + let node_identity = delegations.iter().next()?.node_identity(); + if delegations + .iter() + .any(|delegation| delegation.node_identity() != node_identity) + { + log::warn!("Unexpected different node identities when summing delegations"); + return None; + } + Some(node_identity) +} + +fn get_common_denom(delegations: &[Delegation]) -> Option { + let denom = delegations.iter().next()?.amount.denom.clone(); + if delegations + .iter() + .any(|delegation| delegation.amount.denom != denom) + { + log::warn!("Unexpected different coin denom when summing delegations"); + return None; + } + Some(denom) +} From a6a5ffce68193dfcf4595b6c2a008b4a3318a497 Mon Sep 17 00:00:00 2001 From: gala1234 Date: Wed, 1 Jun 2022 14:18:20 +0200 Subject: [PATCH 16/24] explorer: implement SummedDelegations endpoint and display value on frontend --- explorer/src/api/index.ts | 4 ++++ explorer/src/components/MixNodes/BondBreakdown.tsx | 6 +++--- explorer/src/context/mixnode.tsx | 12 ++++++++++++ explorer/src/typeDefs/explorer-api.ts | 7 +++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts index 8c0c087f46..5a9b40ef9a 100644 --- a/explorer/src/api/index.ts +++ b/explorer/src/api/index.ts @@ -13,6 +13,7 @@ import { import { CountryDataResponse, DelegationsResponse, + UniqDelegationsResponse, GatewayResponse, MixNodeDescriptionResponse, MixNodeResponse, @@ -117,6 +118,9 @@ export class Api { static fetchDelegationsById = async (id: string): Promise => (await fetch(`${MIXNODE_API}/${id}/delegations`)).json(); + static fetchUniqDelegationsById = async (id: string): Promise => + (await fetch(`${MIXNODE_API}/${id}/delegations/summed`)).json(); + static fetchStatsById = async (id: string): Promise => (await fetch(`${MIXNODE_API}/${id}/stats`)).json(); diff --git a/explorer/src/components/MixNodes/BondBreakdown.tsx b/explorer/src/components/MixNodes/BondBreakdown.tsx index 7ebcea0f5f..a8fb791e8d 100644 --- a/explorer/src/components/MixNodes/BondBreakdown.tsx +++ b/explorer/src/components/MixNodes/BondBreakdown.tsx @@ -13,7 +13,7 @@ import { currencyToString } from '../../utils/currency'; import { useMixnodeContext } from '../../context/mixnode'; export const BondBreakdownTable: React.FC = () => { - const { mixNode, delegations } = useMixnodeContext(); + const { mixNode, delegations, uniqDelegations } = useMixnodeContext(); const [showDelegations, toggleShowDelegations] = React.useState(false); const [bonds, setBonds] = React.useState({ @@ -155,7 +155,7 @@ export const BondBreakdownTable: React.FC = () => { fontWeight: 400, }} > - {`(${delegations?.data?.length} delegators)`} + {`(${uniqDelegations?.data?.length} delegators)`} @@ -193,7 +193,7 @@ export const BondBreakdownTable: React.FC = () => { - {delegations?.data?.map(({ owner, amount: { amount, denom } }) => ( + {uniqDelegations?.data?.map(({ owner, amount: { amount, denom } }) => ( {owner} diff --git a/explorer/src/context/mixnode.tsx b/explorer/src/context/mixnode.tsx index c84ee60517..6259b8c00d 100644 --- a/explorer/src/context/mixnode.tsx +++ b/explorer/src/context/mixnode.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { ApiState, DelegationsResponse, + UniqDelegationsResponse, MixNodeDescriptionResponse, MixNodeEconomicDynamicsStatsResponse, MixNodeResponseItem, @@ -18,6 +19,7 @@ import { mixNodeResponseItemToMixnodeRowType, MixnodeRowType } from '../componen interface MixnodeState { delegations?: ApiState; + uniqDelegations?: ApiState; description?: ApiState; economicDynamicsStats?: ApiState; mixNode?: ApiState; @@ -55,6 +57,12 @@ export const MixnodeContextProvider: React.FC = ({ 'Failed to fetch delegations for mixnode', ); + const [uniqDelegations, fetchUniqDelegations, clearUniqDelegations] = useApiState( + mixNodeIdentityKey, + Api.fetchUniqDelegationsById, + 'Failed to fetch delegations for mixnode', + ); + const [status, fetchStatus, clearStatus] = useApiState( mixNodeIdentityKey, Api.fetchStatusById, @@ -90,6 +98,7 @@ export const MixnodeContextProvider: React.FC = ({ // when the identity key changes, remove all previous data clearMixnodeById(); clearDelegations(); + clearUniqDelegations(); clearStatus(); clearStats(); clearDescription(); @@ -105,6 +114,7 @@ export const MixnodeContextProvider: React.FC = ({ setMixnodeRow(mixNodeResponseItemToMixnodeRowType(value.data)); Promise.all([ fetchDelegations(), + fetchUniqDelegations(), fetchStatus(), fetchStats(), fetchDescription(), @@ -117,6 +127,7 @@ export const MixnodeContextProvider: React.FC = ({ const state = React.useMemo( () => ({ delegations, + uniqDelegations, mixNode, mixNodeRow, description, @@ -128,6 +139,7 @@ export const MixnodeContextProvider: React.FC = ({ [ { delegations, + uniqDelegations, mixNode, mixNodeRow, description, diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 36dd210181..944e04fc89 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -170,8 +170,15 @@ export type Delegation = { block_height: number; }; +export type DelegationUniq = { + owner: string; + amount: Amount; +}; + export type DelegationsResponse = Delegation[]; +export type UniqDelegationsResponse = DelegationUniq[]; + export interface CountryDataResponse { [threeLetterCountryCode: string]: CountryData; } From 066aded9bb43e59c7b209d4033de80cb2cb2f4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Walther?= Date: Wed, 1 Jun 2022 14:54:22 +0200 Subject: [PATCH 17/24] Modify runner label --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2a7a76afd..08f18a7397 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: [ self-hosted, custom-linux-exoscale ] + runs-on: [ self-hosted, custom-linux ] # Enable sccache via environment variable env: RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache From 960305b54ef447cc6ce557e4175cb1e5d27521d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Walther?= Date: Wed, 1 Jun 2022 15:20:40 +0200 Subject: [PATCH 18/24] Modify runner label --- .github/workflows/build_on_dispatch.yml | 2 +- .github/workflows/wallet.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_on_dispatch.yml b/.github/workflows/build_on_dispatch.yml index 5e5af95685..3565f1dcbc 100644 --- a/.github/workflows/build_on_dispatch.yml +++ b/.github/workflows/build_on_dispatch.yml @@ -4,7 +4,7 @@ on: workflow_dispatch jobs: build: - runs-on: [ self-hosted, custom-linux-exoscale ] + runs-on: [ self-hosted, custom-linux ] # Enable sccache via environment variable env: RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache diff --git a/.github/workflows/wallet.yml b/.github/workflows/wallet.yml index 2dbc56b817..8c2570ed13 100644 --- a/.github/workflows/wallet.yml +++ b/.github/workflows/wallet.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: [ self-hosted, custom-linux-exoscale ] + runs-on: [ self-hosted, custom-linux ] steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools From 5f7b3db9a40487ee852d292f54922c72100a2e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 2 Jun 2022 16:54:59 +0300 Subject: [PATCH 19/24] Feature/spend coconut (#1261) * Add coconut verifier structure for coconut protocol in gateway * Add endpoint for validator-api cred verification * Remove unused signature field * Register new endpoint * Improve validator-api config handling * Aggregate verif result from all apis * Simplify aggregate functions * Verify cred on apis correctly * Introduced coconut bandwidth contract to validator client * Fix rebase double import * Fix clippy on non-coconut * Add multisig contract address to validator client * Refactor Credential struct * Do bincode magic in the coconut interface * Implement serialization for credential and remove bindcode * Fix clippy and don't remove dkg * Client release funds proposal * Add wrapper for blinded serial number Also compare theta with a blinded serial number (in base 58 form) for future double spend protection. * Only post blinded serial number to blockchain * Validator api propose credential spending * Fix wallet * Gateway calls proposal creation * Query for proposal in verify coconut * Remove db from git * Verify against proposal description * Validator apis vote based on verification of cred * Fix wallet fmt * Execute the release of funds * Fix translation between token and bytes * Update CHANGELOG --- CHANGELOG.md | 3 + Cargo.lock | 54 +++- Cargo.toml | 1 + clients/credential/Cargo.toml | 1 - clients/credential/credential.db | 1 - clients/credential/src/client.rs | 51 ++-- clients/credential/src/commands.rs | 2 +- .../client-libs/gateway-client/src/client.rs | 7 +- .../client-libs/validator-client/Cargo.toml | 3 + .../validator-client/src/client.rs | 146 ++++++----- .../validator-client/src/connection_tester.rs | 7 +- .../src/nymd/cosmwasm_client/logs.rs | 2 +- .../validator-client/src/nymd/error.rs | 6 + .../validator-client/src/nymd/mod.rs | 229 ++++++++++------- .../coconut_bandwidth_signing_client.rs | 49 ++++ .../validator-client/src/nymd/traits/mod.rs | 6 + .../src/nymd/traits/multisig_query_client.rs | 24 ++ .../nymd/traits/multisig_signing_client.rs | 116 +++++++++ .../src/nymd/traits/vesting_query_client.rs | 26 +- .../src/nymd/traits/vesting_signing_client.rs | 34 +-- .../validator-client/src/validator_api/mod.rs | 57 ++++- .../src/validator_api/routes.rs | 3 + common/coconut-interface/src/error.rs | 8 +- common/coconut-interface/src/lib.rs | 237 ++++++++++++++---- .../multisig-contract/Cargo.toml | 14 ++ .../multisig-contract/src/lib.rs | 1 + .../multisig-contract}/src/msg.rs | 4 + common/credentials/src/coconut/utils.rs | 19 +- common/credentials/src/error.rs | 6 +- common/network-defaults/src/all.rs | 8 + common/network-defaults/src/lib.rs | 12 +- common/network-defaults/src/mainnet.rs | 3 + common/network-defaults/src/qa.rs | 3 + common/network-defaults/src/sandbox.rs | 3 + common/nymcoconut/src/proofs/mod.rs | 3 +- common/nymcoconut/src/scheme/double_use.rs | 49 ++++ common/nymcoconut/src/scheme/mod.rs | 4 +- common/nymcoconut/src/scheme/verification.rs | 17 +- contracts/Cargo.lock | 13 + .../multisig/cw3-flex-multisig/Cargo.toml | 2 + .../cw3-flex-multisig/src/contract.rs | 4 +- .../multisig/cw3-flex-multisig/src/lib.rs | 1 - explorer-api/src/client.rs | 11 +- gateway/gateway-requests/Cargo.toml | 1 - .../src/registration/handshake/error.rs | 5 - gateway/gateway-requests/src/types.rs | 22 +- gateway/src/node/client_handling/bandwidth.rs | 18 +- .../connection_handler/authenticated.rs | 70 +++++- .../websocket/connection_handler/coconut.rs | 27 ++ .../connection_handler/eth_events.rs | 36 ++- .../websocket/connection_handler/fresh.rs | 14 +- .../websocket/connection_handler/mod.rs | 2 + .../client_handling/websocket/listener.rs | 11 +- gateway/src/node/mod.rs | 33 ++- mixnode/src/node/mod.rs | 10 +- nym-wallet/Cargo.lock | 60 +++++ nym-wallet/src-tauri/src/config/mod.rs | 15 +- .../src/operations/mixnet/account.rs | 18 +- .../src/operations/simulate/admin.rs | 2 +- .../src/operations/simulate/mixnet.rs | 14 +- .../src/operations/simulate/vesting.rs | 12 +- .../src/operations/vesting/delegate.rs | 2 +- nym-wallet/src/types/rust/gateway.ts | 11 +- nym-wallet/src/types/rust/mixnode.ts | 12 +- .../src/types/rust/rewardedsetnodestatus.ts | 3 +- validator-api/Cargo.toml | 1 + validator-api/src/coconut/client.rs | 14 +- validator-api/src/coconut/error.rs | 14 ++ validator-api/src/coconut/mod.rs | 109 +++++++- validator-api/src/coconut/tests.rs | 46 +++- validator-api/src/config/mod.rs | 75 ++++-- validator-api/src/config/template.rs | 35 ++- validator-api/src/main.rs | 77 +++--- validator-api/src/nymd_client.rs | 123 ++++++--- .../src/rewarded_set_updater/error.rs | 3 - 75 files changed, 1580 insertions(+), 565 deletions(-) delete mode 100644 clients/credential/credential.db create mode 100644 common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs create mode 100644 common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs create mode 100644 common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs create mode 100644 common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml create mode 100644 common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs rename {contracts/multisig/cw3-flex-multisig => common/cosmwasm-smart-contracts/multisig-contract}/src/msg.rs (94%) create mode 100644 common/nymcoconut/src/scheme/double_use.rs create mode 100644 gateway/src/node/client_handling/websocket/connection_handler/coconut.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa72933a7..50dc0d6fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - 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]). +- gateway: Added gateway coconut verifications and validator-api communication for double spending protection ([#1261]) +- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261]) ### Fixed @@ -37,6 +39,7 @@ [#1256]: https://github.com/nymtech/nym/pull/1256 [#1257]: https://github.com/nymtech/nym/pull/1257 [#1260]: https://github.com/nymtech/nym/pull/1260 +[#1261]: https://github.com/nymtech/nym/pull/1261 [#1265]: https://github.com/nymtech/nym/pull/1265 [#1267]: https://github.com/nymtech/nym/pull/1267 [#1275]: https://github.com/nymtech/nym/pull/1275 diff --git a/Cargo.lock b/Cargo.lock index ccc731321e..5df601a7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,7 +873,6 @@ dependencies = [ "bip39", "cfg-if 0.1.10", "clap 3.1.8", - "coconut-bandwidth-contract-common", "coconut-interface", "credential-storage", "credentials", @@ -1166,6 +1165,42 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw3" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw4" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + [[package]] name = "darling" version = "0.13.4" @@ -1951,7 +1986,6 @@ dependencies = [ name = "gateway-requests" version = "0.1.0" dependencies = [ - "bincode", "bs58", "coconut-interface", "credentials", @@ -2910,6 +2944,18 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", +] + [[package]] name = "native-tls" version = "0.2.10" @@ -3226,6 +3272,7 @@ dependencies = [ "humantime-serde", "log", "mixnet-contract-common", + "multisig-contract-common", "nymcoconut", "nymsphinx", "okapi", @@ -6203,16 +6250,19 @@ dependencies = [ "async-trait", "base64", "bip39", + "coconut-bandwidth-contract-common", "coconut-interface", "colored", "config", "cosmrs", "cosmwasm-std", + "cw3", "flate2", "futures", "itertools", "log", "mixnet-contract-common", + "multisig-contract-common", "network-defaults", "prost 0.10.3", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index ac68ef15a0..131fd2dbb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "common/cosmwasm-smart-contracts/coconut-bandwidth-contract", "common/cosmwasm-smart-contracts/contracts-common", "common/cosmwasm-smart-contracts/mixnet-contract", + "common/cosmwasm-smart-contracts/multisig-contract", "common/cosmwasm-smart-contracts/vesting-contract", "common/mixnode-common", "common/network-defaults", diff --git a/clients/credential/Cargo.toml b/clients/credential/Cargo.toml index cfd7dc97f7..54fdb0ee21 100644 --- a/clients/credential/Cargo.toml +++ b/clients/credential/Cargo.toml @@ -17,7 +17,6 @@ thiserror = "1.0" url = "2.2" tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime -coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } coconut-interface = { path = "../../common/coconut-interface" } credentials = { path = "../../common/credentials" } credential-storage = { path = "../../common/credential-storage" } diff --git a/clients/credential/credential.db b/clients/credential/credential.db deleted file mode 100644 index cdc6e614b3..0000000000 --- a/clients/credential/credential.db +++ /dev/null @@ -1 +0,0 @@ -[{"1C703C0BF3FC559E09E3DFB9846A9194191915E55A03F999EDC9279319CEBDDB":"{\"amount\":1000000,\"tx_hash\":\"1C703C0BF3FC559E09E3DFB9846A9194191915E55A03F999EDC9279319CEBDDB\",\"signing_keypair\":{\"public_key\":\"CacF8UyTpcxN1eueg8MG4dF5Trf74ZX1joWdGyuCQSYw\",\"private_key\":\"F8rkWjiEeNqXGY2dApxMdF9j15mn6ErhsAfBnmX3AMnX\"},\"encryption_keypair\":{\"public_key\":\"2Gvhyib4CJx3AVb8bg1fThPJ1FdySoapjgW8AzFyqymC\",\"private_key\":\"7WmF61KHWxiauzR4r82fK7NR8V7t5dbp2FrafP9KfYQx\"},\"blind_request_data\":{\"serial_number\":[217,158,131,252,67,46,125,220,192,31,52,16,80,226,101,249,38,236,180,207,127,126,24,218,158,216,7,175,240,195,13,93],\"binding_number\":[190,14,43,219,219,238,38,109,64,129,247,215,26,111,122,225,142,158,111,115,187,225,250,248,43,100,216,89,87,75,229,40],\"first_attribute\":[108,111,200,242,190,67,6,60,28,121,105,77,36,198,104,18,146,110,173,47,3,55,92,195,251,51,155,86,30,2,181,90],\"second_attribute\":[26,54,139,25,107,59,204,245,146,62,178,206,95,198,20,95,44,70,32,212,88,167,204,255,51,158,246,232,121,69,16,51],\"blind_sign_req\":[185,251,71,165,120,153,76,190,174,242,198,198,141,177,212,57,39,196,3,42,196,120,132,7,53,141,132,255,228,157,26,20,99,212,42,191,44,17,36,61,240,224,34,224,94,78,90,138,162,198,53,118,137,234,10,52,230,147,204,10,109,217,71,227,216,255,39,7,208,11,37,176,177,195,165,12,99,177,188,217,201,54,220,215,217,191,251,37,37,24,71,45,140,66,222,236,2,0,0,0,0,0,0,0,175,191,107,206,203,123,5,142,154,151,146,69,203,158,133,118,60,247,48,204,14,167,114,15,129,190,148,183,252,35,82,22,126,151,42,197,43,177,192,193,84,156,68,244,122,228,14,190,145,147,5,150,242,184,228,43,251,20,165,25,97,236,201,237,40,47,157,235,165,92,9,192,34,83,204,136,91,226,97,11,245,8,219,50,22,85,192,169,35,31,118,4,144,9,80,70,222,110,208,77,182,13,80,85,95,165,150,127,48,181,210,180,74,240,173,43,118,215,55,146,218,157,130,108,202,139,154,15,201,88,253,134,208,203,164,215,91,243,24,70,28,129,137,249,47,131,77,91,75,32,37,192,154,13,95,110,91,241,219,113,2,0,0,0,0,0,0,0,73,102,227,42,178,212,22,215,219,83,51,155,135,226,79,50,158,48,34,141,160,239,129,217,207,157,209,74,167,156,90,88,215,245,3,108,147,132,224,225,40,189,35,225,253,180,135,247,45,67,64,217,83,75,160,14,207,75,116,210,220,192,189,3,2,0,0,0,0,0,0,0,18,196,183,38,67,8,219,14,183,60,138,123,31,55,31,18,151,74,184,0,61,84,35,173,192,178,50,117,229,225,148,6,104,98,54,139,122,40,129,216,190,185,184,135,121,241,170,207,59,190,203,94,212,222,34,27,160,152,233,52,241,175,84,67]},\"signature\":\"y59zgkBne96tAKREkDQ5swA4mCimpooVLyLNAL3jzdVXVHHd43SoS56pfnHULzs9yHP3AJnGCR6FLyHKEhBSXPrYfEGSLMdhy9VrjYiyhthmd6BsKWqS9rGAG6PbWt2PpDh\"}","90C9B03B448608B95D13128472789B27399100D18C46A49254CB13AD07A0BE88":"{\"amount\":1000000,\"tx_hash\":\"90C9B03B448608B95D13128472789B27399100D18C46A49254CB13AD07A0BE88\",\"signing_keypair\":{\"public_key\":\"EadiWGJPJozdxSMPK3r1sr8oHZEcVQU5qWUmqAn7jDNQ\",\"private_key\":\"ASQHYmsq4X1eMEuQvFwGiunXuZxEzm79PPydmY6CKWZt\"},\"encryption_keypair\":{\"public_key\":\"3d4jyDrdepHZPRQo6KzQFpR5kvPySqYqWC19H51cjhMh\",\"private_key\":\"HDMhDCTiTu5ChUxWJqZRCR7vZnYULyTu5Rx5Vh2VNxTm\"},\"blind_request_data\":{\"serial_number\":[153,108,141,81,89,232,4,244,22,216,186,112,32,142,11,42,87,74,40,216,191,253,127,49,67,243,183,188,162,46,221,75],\"binding_number\":[7,68,244,43,146,101,216,254,14,27,164,250,41,195,187,242,189,144,126,105,179,35,128,247,30,101,200,151,235,224,92,80],\"first_attribute\":[175,216,242,214,61,233,96,76,17,254,79,233,23,217,84,94,32,68,225,249,201,46,109,183,90,63,126,121,114,87,239,44],\"second_attribute\":[180,82,202,163,28,149,147,178,186,66,26,149,55,177,45,154,185,20,62,68,123,70,188,73,28,146,99,235,91,225,49,107],\"blind_sign_req\":[153,12,234,149,97,21,0,51,175,113,17,215,121,255,21,185,27,59,204,115,208,171,136,219,140,125,151,168,85,240,167,222,13,162,250,39,50,74,172,251,237,85,136,41,34,71,198,215,151,16,80,136,115,178,27,230,102,25,4,12,17,204,225,49,67,109,125,119,100,204,201,90,210,111,118,15,146,244,29,130,64,181,67,178,222,190,27,122,12,47,56,63,187,66,236,130,2,0,0,0,0,0,0,0,161,138,107,198,253,152,7,86,206,134,122,66,171,152,77,90,251,134,64,95,193,174,162,252,176,193,217,52,36,53,207,237,188,84,208,204,183,94,103,168,118,149,151,175,40,134,171,53,184,102,151,180,126,82,124,138,79,130,214,135,133,54,251,182,165,32,6,4,34,75,153,113,11,49,157,149,28,150,8,71,214,47,224,119,109,16,113,85,140,181,214,194,11,168,127,178,189,117,99,79,90,134,207,33,102,42,232,150,89,209,247,165,80,91,237,42,201,223,187,88,98,217,4,199,226,1,60,15,45,159,103,94,185,43,99,142,109,186,212,240,9,161,247,135,184,165,75,67,32,47,122,196,39,199,184,176,87,130,30,20,2,0,0,0,0,0,0,0,117,234,104,216,238,168,141,1,212,50,149,242,215,231,19,91,45,18,164,168,24,68,40,57,25,141,165,63,152,72,121,27,135,89,68,176,9,8,176,145,131,50,160,195,7,248,80,104,135,142,236,251,21,55,39,170,86,133,224,50,39,88,252,31,2,0,0,0,0,0,0,0,65,93,35,204,132,168,2,49,218,27,106,107,95,165,195,206,45,63,158,197,65,106,131,71,43,76,129,106,87,155,146,97,187,218,99,40,125,68,50,152,115,112,80,96,21,11,203,94,118,186,209,236,234,35,159,178,148,59,238,39,218,225,219,106]},\"signature\":\"u3BgWtmZZoNG9tSkC37Z6s15EZXLkY4PiYJX6HHA4iwHQKqKvNVuruPaRaP5ra9448G8iBDbybL94RSRfRYZGbeheMcnwaynvuUJbVh3KmdGLJR3Yr5UoNNjoLqEDLzP3jV\"}","04296C930C81F37096FD180CD93896A164CF5F999DF7D09C0124BE1DC75A5358":"{\"amount\":1000000,\"tx_hash\":\"04296C930C81F37096FD180CD93896A164CF5F999DF7D09C0124BE1DC75A5358\",\"signing_keypair\":{\"public_key\":\"9qnzPdvyUjgWFj7RPpvfbEkG7CbnUUdfj1Zi4nKA8B9b\",\"private_key\":\"ALa8vt73t6sjgbeHg5C6NuezvHbENMabZ3wzxv47TGyo\"},\"encryption_keypair\":{\"public_key\":\"E15DAJTRDvAj7JfZ7Rn5DUffcu72HVJyqYX5gm9WVPZu\",\"private_key\":\"4GLy6J8FVTDRdNzuKyDRg1BRQXNxA8gCALaTJKfYYkNR\"},\"blind_request_data\":{\"serial_number\":[67,28,114,203,240,92,183,145,79,253,109,235,240,221,156,199,216,66,67,175,124,158,226,171,141,131,244,120,187,230,146,62],\"binding_number\":[129,8,63,239,8,211,192,30,124,87,30,110,102,86,77,95,149,127,177,126,28,196,245,29,63,127,128,236,82,231,207,96],\"first_attribute\":[19,21,49,159,55,242,100,60,218,124,120,115,254,64,144,181,222,30,31,41,163,110,117,11,0,11,206,221,126,242,191,41],\"second_attribute\":[29,63,187,178,21,49,68,13,190,62,208,224,249,34,37,116,201,114,139,210,185,67,187,4,41,166,130,243,171,5,10,50],\"blind_sign_req\":[171,30,44,213,50,220,70,49,93,74,79,21,41,89,12,7,20,204,72,50,202,62,231,87,64,154,132,195,48,55,131,212,232,22,174,42,151,232,149,62,132,149,238,112,133,68,224,197,164,96,56,253,196,195,89,4,213,177,122,252,77,167,117,179,245,15,85,100,250,103,77,53,61,61,123,229,206,157,80,36,36,254,220,48,88,110,197,43,117,255,59,58,140,60,99,116,2,0,0,0,0,0,0,0,137,28,145,218,237,220,203,213,63,138,166,104,255,246,227,123,203,7,135,135,17,186,27,83,222,28,234,44,53,189,72,192,238,95,141,136,30,65,57,226,125,137,230,137,165,111,183,73,147,55,3,79,148,223,224,23,198,63,0,58,38,228,229,26,206,77,214,109,208,178,255,168,27,124,121,219,174,252,174,110,71,121,172,24,65,222,31,137,239,55,117,144,153,19,54,107,146,125,29,228,135,153,81,207,193,104,103,17,31,180,67,194,197,205,54,14,225,96,193,141,145,121,219,226,227,196,200,37,110,168,46,27,73,173,238,35,26,59,50,148,24,224,90,88,145,12,206,181,108,216,217,49,19,4,105,249,48,205,167,61,2,0,0,0,0,0,0,0,230,46,74,45,156,42,223,24,154,49,207,38,172,223,75,51,137,46,151,242,155,118,37,115,132,207,113,134,124,78,244,59,181,239,71,132,221,190,90,37,6,50,91,214,82,47,39,151,91,78,249,241,56,242,144,138,9,127,32,51,36,164,155,98,2,0,0,0,0,0,0,0,108,190,2,160,139,55,81,228,126,190,247,27,209,83,87,146,171,103,186,168,4,119,76,165,105,105,67,43,159,106,61,87,221,24,235,97,67,137,80,143,180,205,102,162,244,126,43,144,21,243,251,236,15,174,158,163,180,20,123,72,235,124,31,57]},\"signature\":\"yd9yDABbw8c3wgT8Pof7Z2jidAJs8RoL8KbYH2F1tmrMMCJwhUMzSjpyma6kS9WgaJsFBEbNXpwH1ip6LETfPgvfUJJo2Z9c5312TMAfR8qFr4Zc3X9aQyAo8kMvijCT7jA\"}","5E3CF2741F73091EA9B651B271D9925D6FBE3F67E9C5D3793303DA6D44F12FE5":"{\"amount\":1000000,\"tx_hash\":\"5E3CF2741F73091EA9B651B271D9925D6FBE3F67E9C5D3793303DA6D44F12FE5\",\"signing_keypair\":{\"public_key\":\"AEu3bhLcRZBxjoDVaTmpbNqPhyXRuiN5RQAhoRhcwjxt\",\"private_key\":\"DoEaH8EnoXef7SDyuSTuFt5CMWMdErWrBVfRrpnhmJYq\"},\"encryption_keypair\":{\"public_key\":\"HAW7XdJUMGV5fWAQLpZnHMX7nEnGJsRdgekVgYiqzwPL\",\"private_key\":\"G8wSowRi8weKD2dnM8YBvaGB94FEUrmsxLVQK7x6wz4k\"},\"blind_request_data\":{\"serial_number\":[184,233,37,105,38,174,157,246,96,46,155,173,169,113,202,128,121,35,166,191,146,13,251,46,141,104,88,140,102,165,124,33],\"binding_number\":[46,252,137,11,240,92,139,154,129,213,239,195,94,37,102,119,146,151,182,103,77,171,231,223,13,235,14,240,134,92,29,112],\"first_attribute\":[81,14,113,15,19,75,0,228,7,169,12,125,185,125,247,228,63,164,101,82,90,68,65,89,178,210,131,190,14,66,152,55],\"second_attribute\":[174,224,189,178,99,214,9,196,186,65,150,19,176,132,244,148,52,17,138,132,35,205,216,120,59,138,48,210,149,139,35,51],\"blind_sign_req\":[171,19,145,159,249,120,163,237,106,166,105,120,112,17,203,245,126,95,37,190,221,18,186,211,218,99,1,138,95,12,189,224,120,114,175,30,76,87,106,207,58,154,38,30,109,233,29,16,151,198,168,43,105,60,149,9,167,47,211,231,33,186,239,191,191,118,37,242,211,204,130,120,78,11,51,132,62,148,73,186,225,81,140,67,32,64,90,253,196,89,74,31,1,165,183,22,2,0,0,0,0,0,0,0,167,72,204,0,179,90,227,17,51,115,156,33,198,76,219,218,168,111,226,107,228,150,49,239,252,92,166,195,133,251,3,13,167,75,235,27,132,162,11,238,215,48,32,222,253,156,10,133,148,143,7,75,15,86,228,116,119,12,48,63,189,1,131,94,46,245,82,150,198,204,251,125,76,147,205,235,95,153,177,161,233,11,49,243,106,216,201,6,31,110,185,11,166,171,42,143,5,127,215,168,27,229,207,225,197,236,87,213,24,123,205,174,250,156,178,255,204,108,198,150,53,188,144,14,217,234,92,80,123,101,99,157,7,197,207,108,35,125,251,52,180,185,67,155,220,175,32,116,202,128,93,92,77,95,79,205,62,99,110,13,2,0,0,0,0,0,0,0,10,126,251,84,164,181,206,92,82,231,45,58,6,5,79,13,255,73,78,157,121,48,109,118,27,225,108,237,11,228,105,78,177,226,89,24,13,31,52,160,195,224,191,232,115,107,242,8,173,82,83,32,37,118,141,172,217,238,110,243,141,174,211,62,2,0,0,0,0,0,0,0,163,242,24,184,82,249,187,221,4,105,16,203,144,106,58,115,50,23,107,30,252,71,101,155,37,5,227,23,122,10,252,65,18,113,129,88,56,199,169,202,73,196,151,1,186,168,155,7,68,165,186,157,18,222,194,25,194,58,49,125,114,43,212,4]},\"signature\":\"uHR5LkaHQc3LQUXuzydXLj783vsJtp2s5FV1CNRQ3g8rjL29U2UM3wSEFWrxjoFVq5gS8sNUEDbvhGTehWM3pGPRMVY4U68gkPWzzR7dAMX1LHYjwUdUiwNgURmgVFrG7XV\"}","3A24DE7C3C1EF38F6CBEBB1BE893EB6819E55A9C8E8164C55A9B75CE42950536":"{\"amount\":1000000,\"tx_hash\":\"3A24DE7C3C1EF38F6CBEBB1BE893EB6819E55A9C8E8164C55A9B75CE42950536\",\"signing_keypair\":{\"public_key\":\"91wBGhrPnLAM7rc8C1WK693VwfZKj7d9K7Rpfcpxo3x7\",\"private_key\":\"FEh9rCDMZx29nZwTyjezxtcytRiZZrsxM2qWdyMyuEC7\"},\"encryption_keypair\":{\"public_key\":\"9Ay88XFrErtSLeeD3DQETeaqrAgAKNR1vNjoe2PvzyoD\",\"private_key\":\"HD92abV3ZPW1VKxh3bCcHVZ8oJ5XqRjrKspuZkQMvqkT\"},\"blind_request_data\":{\"serial_number\":[132,225,105,217,220,29,178,224,94,120,11,53,170,48,254,118,226,13,138,175,1,137,6,233,199,243,82,177,15,236,8,5],\"binding_number\":[214,221,36,34,246,89,38,85,207,68,157,111,72,61,241,253,70,30,206,120,80,185,10,217,210,162,9,77,57,112,79,112],\"first_attribute\":[213,53,84,155,222,190,233,7,127,153,97,181,71,169,88,180,33,255,144,158,221,62,119,61,11,183,140,136,198,152,186,21],\"second_attribute\":[231,35,35,8,13,210,132,121,142,177,211,196,174,183,10,161,8,232,101,123,107,10,144,104,58,212,95,243,216,98,91,66],\"blind_sign_req\":[131,90,160,125,41,86,193,108,153,195,118,191,72,114,19,168,89,70,211,33,230,221,202,233,172,194,91,92,168,79,223,65,217,200,186,142,16,136,216,83,40,22,53,249,50,29,234,219,174,135,50,180,67,16,30,73,89,232,51,155,170,105,83,240,116,155,50,214,28,98,241,211,27,202,201,186,231,151,9,249,149,50,246,24,204,81,141,77,51,32,90,254,63,150,35,228,2,0,0,0,0,0,0,0,141,71,143,23,11,175,34,130,24,40,235,202,85,74,58,208,132,184,176,43,159,38,159,138,118,16,141,70,20,77,59,12,153,184,153,255,181,243,192,131,239,253,207,58,228,82,207,244,143,126,159,156,127,116,223,200,175,191,130,91,218,19,169,222,95,36,151,19,18,14,236,135,21,30,69,130,146,236,252,85,152,68,107,154,102,48,200,246,88,205,96,74,12,203,10,2,88,70,108,249,72,221,172,76,212,26,105,127,1,214,165,143,133,241,142,106,52,190,152,116,101,26,181,213,208,59,117,33,128,70,252,146,203,142,173,98,252,135,68,44,197,243,225,52,170,142,92,167,250,23,211,161,7,85,102,108,15,164,238,94,2,0,0,0,0,0,0,0,4,48,16,6,220,119,137,9,19,83,33,21,156,10,94,247,114,65,199,182,184,76,83,122,118,46,29,9,111,242,244,4,195,174,63,91,223,161,105,250,195,94,75,226,206,80,226,190,37,203,200,184,173,164,170,226,27,244,43,100,95,65,40,45,2,0,0,0,0,0,0,0,136,46,57,4,42,1,115,237,142,99,92,81,193,81,203,189,226,255,137,255,109,205,71,147,74,191,253,120,93,154,61,18,139,163,156,182,203,137,253,197,11,225,221,81,59,38,23,43,168,53,214,23,140,227,39,174,93,191,83,150,228,30,41,12]},\"signature\":\"237zXmYcb2de47menhqKVUq75YUQYmDfcZ2Cnpqf7c69i1dpK4DetfzxUmsZxkoEw3pfBZkbkZdAUHbXpKJRaVG9RVy13gucaiZVpnmrdHmyqsfUJYqxB5XJy7PvnQufvW1p\"}","AB1DB6BCE0E5BB8093CD83CE8B979A38ABA549017F1E5FBCFC6665D8301F8B10":"{\"amount\":1000000,\"tx_hash\":\"AB1DB6BCE0E5BB8093CD83CE8B979A38ABA549017F1E5FBCFC6665D8301F8B10\",\"signing_keypair\":{\"public_key\":\"BsW9Zi5fr6MVXVD9C79DHazZngKYVU29Dd2FqiB3t6GC\",\"private_key\":\"5t6asn1vRZunc4tna9doZoURJJyVsugmkDwgaQe6fmZg\"},\"encryption_keypair\":{\"public_key\":\"Gx3p6FJxBs7uEAQPWFmVwiysPBeRseC5XvUixiNFFeAt\",\"private_key\":\"CKV5eAdhsmyBijmnt1UM7wLTmxnLxbV6h2WY6JFZmYVm\"},\"blind_request_data\":{\"serial_number\":[98,22,102,53,67,142,112,200,210,202,159,104,167,22,128,165,252,131,183,212,4,150,175,203,141,146,137,193,136,219,228,67],\"binding_number\":[3,214,193,92,3,51,114,82,60,25,195,249,143,244,252,169,19,84,168,119,227,141,186,35,187,120,96,205,72,61,189,48],\"first_attribute\":[132,126,154,107,2,30,197,238,4,205,66,148,113,208,48,233,37,53,187,160,213,151,49,171,64,57,139,90,63,87,188,115],\"second_attribute\":[106,78,124,199,142,38,90,198,100,219,16,74,9,109,47,212,166,30,185,234,204,112,42,38,193,74,135,219,223,179,30,19],\"blind_sign_req\":[145,90,96,73,107,35,232,107,92,90,101,110,229,20,12,197,185,112,19,110,4,212,236,222,33,252,156,12,164,253,198,80,41,254,246,26,247,150,242,56,2,250,155,142,20,132,108,172,178,146,141,193,156,246,79,110,32,254,166,217,57,83,8,70,58,42,48,48,137,96,13,193,214,125,89,122,85,97,13,112,29,193,119,165,251,204,221,81,138,206,108,3,4,88,182,27,2,0,0,0,0,0,0,0,131,68,160,172,144,26,48,222,7,103,95,220,238,3,125,119,15,254,236,186,107,240,69,183,43,182,191,139,208,201,100,128,55,146,133,119,213,155,40,224,106,149,217,30,147,95,52,7,146,147,130,119,30,74,157,239,231,11,199,90,188,64,201,48,27,156,69,63,33,107,61,254,128,200,127,207,97,179,198,142,223,95,167,197,54,54,54,28,122,22,99,197,213,105,56,211,204,241,133,3,184,107,81,82,10,67,188,3,221,93,229,232,125,173,124,242,248,183,14,124,52,120,234,79,175,133,191,33,45,79,152,252,100,22,220,193,244,169,43,29,202,104,2,96,164,95,116,46,5,23,50,139,39,23,73,151,94,229,153,21,2,0,0,0,0,0,0,0,90,175,157,204,225,13,7,24,166,246,95,121,101,58,127,194,67,222,14,175,13,126,221,162,91,86,130,91,249,183,86,48,164,151,67,190,88,250,83,103,236,57,114,53,155,50,92,181,58,239,141,200,153,16,44,83,110,125,190,133,195,145,165,98,2,0,0,0,0,0,0,0,45,195,137,247,231,86,230,71,21,193,215,245,38,74,249,49,81,90,142,237,9,123,34,252,140,107,120,2,118,147,121,107,65,0,99,209,123,64,124,244,142,136,130,186,77,32,148,12,216,71,76,245,179,241,249,177,44,191,107,95,159,123,125,52]},\"signature\":\"24WoBrFcv1mF8fL2MH1YHnbZq69G44ZqSzbdA8ciEWY8JXTFn1G6yyKGEUkxcmLoL7r5j9fPJD4mTZUdiRQ87pLMZPCkp6M9bLZMwD7b4u2Cg8CNmVUpaZ4VbNdTf4zhZid5\"}","90B33BF08C84575E88C6ACFFE6F44DFC652BF107CDC2D541BFCA8BAC931E5BA8":"{\"amount\":1000000,\"tx_hash\":\"90B33BF08C84575E88C6ACFFE6F44DFC652BF107CDC2D541BFCA8BAC931E5BA8\",\"signing_keypair\":{\"public_key\":\"4o64HhsgHq6bCWERouYe8wLGdrBW6Zc87uRFsRyQ6KhX\",\"private_key\":\"9pcuX5cpKxGde6ZiPbsMbSKBx8qcnbSD5VkV68VuNQB3\"},\"encryption_keypair\":{\"public_key\":\"2cF4745DbeYYxymmPJYTu9R88wRVSHnC7rs4TPvLkNxm\",\"private_key\":\"6wyfg3FEwbtWJ4pAfwEVvVuo4gEJV6cc4Kj6LV3qy2WV\"},\"blind_request_data\":{\"serial_number\":[95,129,65,207,163,115,235,106,18,228,138,56,255,52,168,5,190,226,124,190,211,158,70,124,38,235,132,6,25,222,157,31],\"binding_number\":[147,54,129,176,80,3,106,255,93,179,219,233,158,175,141,145,180,245,103,169,36,3,99,138,7,139,108,186,186,193,152,17],\"first_attribute\":[61,240,7,131,88,85,251,196,132,143,227,154,197,40,194,163,27,24,53,182,97,31,61,238,241,172,115,236,99,2,240,15],\"second_attribute\":[203,202,64,166,201,186,160,157,155,156,127,116,239,44,158,213,167,57,152,228,39,153,194,20,46,222,44,246,87,151,164,61],\"blind_sign_req\":[146,176,228,40,252,11,192,170,3,84,188,29,87,64,49,134,204,118,180,111,166,23,44,91,115,7,5,41,143,230,192,139,183,60,135,118,68,33,28,56,245,7,61,128,246,183,224,155,149,180,159,139,44,6,86,185,32,243,35,50,208,167,105,51,10,199,17,25,202,242,82,185,180,93,181,71,9,21,198,3,53,27,228,247,70,247,15,231,35,239,171,168,12,39,9,103,2,0,0,0,0,0,0,0,149,10,49,53,253,27,20,83,77,16,209,183,26,202,173,245,48,2,98,214,50,111,205,61,143,205,180,12,60,80,204,8,225,176,250,242,51,147,6,172,49,20,234,128,58,157,77,9,173,73,60,198,152,161,17,123,43,108,134,157,145,182,38,23,7,244,179,16,244,222,206,4,241,95,210,165,18,216,116,249,134,68,231,240,47,49,30,18,236,98,51,218,81,213,208,120,166,39,74,65,151,62,195,247,123,40,224,232,120,182,234,214,63,185,102,225,19,65,65,28,233,91,168,201,249,193,43,59,2,192,145,29,106,92,124,57,210,121,76,30,81,115,226,84,155,68,97,46,82,34,160,51,76,110,123,248,55,124,137,102,2,0,0,0,0,0,0,0,237,8,254,97,90,184,163,156,221,82,133,94,177,40,166,132,230,14,241,234,176,194,121,0,157,148,70,12,157,15,160,28,212,94,219,169,220,163,101,233,97,95,106,244,208,115,209,2,3,150,129,201,126,49,98,125,26,68,122,103,142,111,23,21,2,0,0,0,0,0,0,0,93,65,229,251,193,25,208,246,197,112,140,170,138,227,58,92,23,231,190,23,95,49,20,247,18,65,168,78,40,181,74,69,7,14,136,243,70,224,141,79,202,202,127,255,64,28,8,79,69,17,47,130,129,224,103,10,50,89,97,99,93,242,119,14]},\"signature\":\"ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZLZR9j\"}"},{}] \ No newline at end of file diff --git a/clients/credential/src/client.rs b/clients/credential/src/client.rs index 40f035c6b9..dae2d9e91f 100644 --- a/clients/credential/src/client.rs +++ b/clients/credential/src/client.rs @@ -1,60 +1,49 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -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 std::str::FromStr; use url::Url; -use validator_client::nymd::{AccountId, Coin, Denom, NymdClient, SigningNymdClient}; + +use crate::error::Result; +use crate::{MNEMONIC, NYMD_URL}; + +use network_defaults::{DEFAULT_NETWORK, DENOM, VOUCHER_INFO}; +use validator_client::nymd::traits::CoconutBandwidthSigningClient; +use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient}; pub(crate) struct Client { nymd_client: NymdClient, - denom: Denom, - contract_address: AccountId, } impl Client { pub fn new() -> Self { let nymd_url = Url::from_str(NYMD_URL).unwrap(); let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap(); - let nymd_client = NymdClient::connect_with_mnemonic( - DEFAULT_NETWORK, - nymd_url.as_ref(), - None, - None, - None, - mnemonic, - None, - ) - .unwrap(); - let denom = Denom::from_str(network_defaults::DENOM).unwrap(); - let contract_address = AccountId::from_str(CONTRACT_ADDRESS).unwrap(); + let nymd_client = + NymdClient::connect_with_mnemonic(DEFAULT_NETWORK, nymd_url.as_ref(), mnemonic, None) + .unwrap(); - Client { - nymd_client, - denom, - contract_address, - } + Client { nymd_client } } pub async fn deposit( &self, amount: u64, - info: &str, verification_key: String, encryption_key: String, + fee: Option, ) -> Result { - let req = ExecuteMsg::DepositFunds { - data: DepositData::new(info.to_string(), verification_key, encryption_key), - }; - let funds = vec![Coin::new(amount as u128, self.denom.to_string())]; + let amount = Coin::new(amount as u128, DENOM.to_string()); Ok(self .nymd_client - .execute(&self.contract_address, &req, Default::default(), "", funds) + .deposit( + amount, + String::from(VOUCHER_INFO), + verification_key, + encryption_key, + fee, + ) .await? .transaction_hash .to_string()) diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs index 62ab440fd2..f95c476120 100644 --- a/clients/credential/src/commands.rs +++ b/clients/credential/src/commands.rs @@ -55,9 +55,9 @@ impl Execute for Deposit { let tx_hash = client .deposit( self.amount, - VOUCHER_INFO, signing_keypair.public_key.clone(), encryption_keypair.public_key.clone(), + None, ) .await?; diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 0435c6cab4..4ead7b4597 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -83,7 +83,7 @@ impl GatewayClient { ) -> Self { GatewayClient { authenticated: false, - disabled_credentials_mode: true, + disabled_credentials_mode: false, bandwidth_remaining: 0, gateway_address, gateway_identity, @@ -100,8 +100,8 @@ impl GatewayClient { } } - pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) { - self.disabled_credentials_mode = disabled_credentials_mode + pub fn set_disabled_credentials_mode(&mut self, _disabled_credentials_mode: bool) { + self.disabled_credentials_mode = false; } // TODO: later convert into proper builder methods @@ -496,7 +496,6 @@ impl GatewayClient { self.shared_key.as_ref().unwrap(), iv, ) - .ok_or(GatewayClientError::SerializeCredential)? .into(); self.bandwidth_remaining = match self.send_websocket_message(msg).await? { ServerResponse::Bandwidth { available_total } => Ok(available_total), diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 3ea58f4212..560dcb4788 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -10,8 +10,11 @@ rust-version = "1.56" [dependencies] base64 = "0.13" colored = "2.0" +cw3 = "0.13.1" mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" } vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" } +coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } +multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } vesting-contract = { path = "../../../contracts/vesting" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index f3f4842013..b28f4e33aa 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -2,7 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{validator_api, ValidatorClientError}; -use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; +use coconut_interface::{ + BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody, + ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse, + VerifyCredentialBody, VerifyCredentialResponse, +}; use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; use url::Url; @@ -29,8 +33,6 @@ use mixnet_contract_common::{ }; #[cfg(feature = "nymd-client")] use std::collections::{HashMap, HashSet}; -#[cfg(feature = "nymd-client")] -use std::str::FromStr; #[cfg(feature = "nymd-client")] #[must_use] @@ -39,9 +41,9 @@ pub struct Config { network: network_defaults::all::Network, api_url: Url, nymd_url: Url, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, + mixnet_contract_address: cosmrs::AccountId, + vesting_contract_address: cosmrs::AccountId, + bandwidth_claim_contract_address: cosmrs::AccountId, mixnode_page_limit: Option, gateway_page_limit: Option, @@ -51,20 +53,22 @@ pub struct Config { #[cfg(feature = "nymd-client")] impl Config { - pub fn new( - network: network_defaults::all::Network, - nymd_url: Url, - api_url: Url, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, - ) -> Self { + pub fn new(network: network_defaults::all::Network, nymd_url: Url, api_url: Url) -> Self { Config { network, nymd_url, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), api_url, mixnode_page_limit: None, gateway_page_limit: None, @@ -73,6 +77,21 @@ impl Config { } } + pub fn with_mixnode_contract_address(mut self, address: cosmrs::AccountId) -> Self { + self.mixnet_contract_address = address; + self + } + + pub fn with_vesting_contract_address(mut self, address: cosmrs::AccountId) -> Self { + self.vesting_contract_address = address; + self + } + + pub fn with_bandwidth_claim_contract_address(mut self, address: cosmrs::AccountId) -> Self { + self.bandwidth_claim_contract_address = address; + self + } + pub fn with_mixnode_page_limit(mut self, limit: Option) -> Config { self.mixnode_page_limit = limit; self @@ -97,9 +116,9 @@ impl Config { #[cfg(feature = "nymd-client")] pub struct Client { pub network: network_defaults::all::Network, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, + mixnet_contract_address: cosmrs::AccountId, + vesting_contract_address: cosmrs::AccountId, + bandwidth_claim_contract_address: cosmrs::AccountId, mnemonic: Option, mixnode_page_limit: Option, @@ -122,18 +141,18 @@ impl Client { let nymd_client = NymdClient::connect_with_mnemonic( config.network, config.nymd_url.as_str(), - config.mixnet_contract_address.clone(), - config.vesting_contract_address.clone(), - config.erc20_bridge_contract_address.clone(), mnemonic.clone(), None, - )?; + )? + .with_mixnet_contract_address(config.mixnet_contract_address.clone()) + .with_vesting_contract_address(config.vesting_contract_address.clone()) + .with_bandwidth_claim_contract_address(config.bandwidth_claim_contract_address.clone()); Ok(Client { network: config.network, mixnet_contract_address: config.mixnet_contract_address, vesting_contract_address: config.vesting_contract_address, - erc20_bridge_contract_address: config.erc20_bridge_contract_address, + bandwidth_claim_contract_address: config.bandwidth_claim_contract_address, mnemonic: Some(mnemonic), mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, @@ -148,12 +167,12 @@ impl Client { self.nymd = NymdClient::connect_with_mnemonic( self.network, new_endpoint.as_ref(), - self.mixnet_contract_address.clone(), - self.vesting_contract_address.clone(), - self.erc20_bridge_contract_address.clone(), self.mnemonic.clone().unwrap(), None, - )?; + )? + .with_mixnet_contract_address(self.mixnet_contract_address.clone()) + .with_vesting_contract_address(self.vesting_contract_address.clone()) + .with_bandwidth_claim_contract_address(self.bandwidth_claim_contract_address.clone()); Ok(()) } @@ -166,32 +185,15 @@ impl Client { impl Client { pub fn new_query(config: Config) -> Result, ValidatorClientError> { let validator_api_client = validator_api::Client::new(config.api_url.clone()); - let nymd_client = NymdClient::connect( - config.nymd_url.as_str(), - Some(config.mixnet_contract_address.clone().unwrap_or_else(|| { - cosmrs::AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).unwrap() - })), - Some(config.vesting_contract_address.clone().unwrap_or_else(|| { - cosmrs::AccountId::from_str(DEFAULT_NETWORK.vesting_contract_address()).unwrap() - })), - Some( - config - .erc20_bridge_contract_address - .clone() - .unwrap_or_else(|| { - cosmrs::AccountId::from_str( - DEFAULT_NETWORK.bandwidth_claim_contract_address(), - ) - .unwrap() - }), - ), - )?; + let nymd_client = NymdClient::connect(config.nymd_url.as_str())? + .with_mixnet_contract_address(config.mixnet_contract_address.clone()) + .with_vesting_contract_address(config.vesting_contract_address.clone()); Ok(Client { network: config.network, mixnet_contract_address: config.mixnet_contract_address, vesting_contract_address: config.vesting_contract_address, - erc20_bridge_contract_address: config.erc20_bridge_contract_address, + bandwidth_claim_contract_address: config.bandwidth_claim_contract_address, mnemonic: None, mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, @@ -203,12 +205,10 @@ impl Client { } pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { - self.nymd = NymdClient::connect( - new_endpoint.as_ref(), - self.mixnet_contract_address.clone(), - self.vesting_contract_address.clone(), - self.erc20_bridge_contract_address.clone(), - )?; + self.nymd = NymdClient::connect(new_endpoint.as_ref())? + .with_mixnet_contract_address(self.mixnet_contract_address.clone()) + .with_vesting_contract_address(self.vesting_contract_address.clone()) + .with_bandwidth_claim_contract_address(self.bandwidth_claim_contract_address.clone()); Ok(()) } } @@ -222,10 +222,10 @@ impl Client { // use case: somebody initialised client without a contract in order to upload and initialise one // and now they want to actually use it without making new client pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) { - self.mixnet_contract_address = Some(mixnet_contract_address) + self.mixnet_contract_address = mixnet_contract_address } - pub fn get_mixnet_contract_address(&self) -> Option { + pub fn get_mixnet_contract_address(&self) -> cosmrs::AccountId { self.mixnet_contract_address.clone() } @@ -733,4 +733,34 @@ impl ApiClient { ) -> Result { Ok(self.validator_api.get_coconut_verification_key().await?) } + + pub async fn verify_bandwidth_credential( + &self, + request_body: &VerifyCredentialBody, + ) -> Result { + Ok(self + .validator_api + .verify_bandwidth_credential(request_body) + .await?) + } + + pub async fn propose_release_funds( + &self, + request_body: &ProposeReleaseFundsRequestBody, + ) -> Result { + Ok(self + .validator_api + .propose_release_funds(request_body) + .await?) + } + + pub async fn execute_release_funds( + &self, + request_body: &ExecuteReleaseFundsRequestBody, + ) -> Result<(), ValidatorClientError> { + Ok(self + .validator_api + .execute_release_funds(request_body) + .await?) + } } diff --git a/common/client-libs/validator-client/src/connection_tester.rs b/common/client-libs/validator-client/src/connection_tester.rs index 1a8154cd80..9e41f91989 100644 --- a/common/client-libs/validator-client/src/connection_tester.rs +++ b/common/client-libs/validator-client/src/connection_tester.rs @@ -19,7 +19,7 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2; pub async fn run_validator_connection_test( nymd_urls: impl Iterator, api_urls: impl Iterator, - mixnet_contract_address: HashMap, H>, + mixnet_contract_address: HashMap, ) -> ( HashMap>, HashMap>, @@ -47,14 +47,15 @@ pub async fn run_validator_connection_test( fn setup_connection_tests( nymd_urls: impl Iterator, api_urls: impl Iterator, - mixnet_contract_address: HashMap, H>, + mixnet_contract_address: HashMap, ) -> impl Iterator { let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| { let address = mixnet_contract_address .get(&network) .expect("No configured contract address") .clone(); - NymdClient::::connect(url.as_str(), address, None, None) + NymdClient::::connect(url.as_str()) + .map(|client| client.with_mixnet_contract_address(address)) .map(move |client| ClientForConnectionTest::Nymd(network, url, Box::new(client))) .ok() }); diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs index 961deffadc..5bf493fbc9 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs @@ -22,7 +22,7 @@ pub struct Log { /// Searches in logs for the first event of the given event type and in that event /// for the first attribute with the given attribute key. -pub(crate) fn find_attribute<'a>( +pub fn find_attribute<'a>( logs: &'a [Log], event_type: &str, attribute_key: &str, diff --git a/common/client-libs/validator-client/src/nymd/error.rs b/common/client-libs/validator-client/src/nymd/error.rs index ea3620ec35..8ccd41c09e 100644 --- a/common/client-libs/validator-client/src/nymd/error.rs +++ b/common/client-libs/validator-client/src/nymd/error.rs @@ -124,6 +124,12 @@ pub enum NymdError { #[error("Transaction with ID {hash} has been submitted but not yet found on the chain. You might want to check for it later. There was a total wait of {} seconds", .timeout.as_secs())] BroadcastTimeout { hash: tx::Hash, timeout: Duration }, + + #[error("Cosmwasm std error: {0}")] + CosmwasmStdError(#[from] cosmwasm_std::StdError), + + #[error("Coconut interface error: {0}")] + CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), } impl NymdError { diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 19d17f390a..1851ce155d 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -23,6 +23,7 @@ use mixnet_contract_common::{ PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, PagedRewardedSetResponse, QueryMsg, RewardedSetUpdateDetails, }; +use network_defaults::DEFAULT_NETWORK; use serde::Serialize; use std::convert::TryInto; @@ -58,30 +59,64 @@ pub mod wallet; #[derive(Debug)] pub struct NymdClient { client: C, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, + mixnet_contract_address: AccountId, + vesting_contract_address: AccountId, + bandwidth_claim_contract_address: AccountId, + coconut_bandwidth_contract_address: AccountId, + multisig_contract_address: AccountId, client_address: Option>, simulated_gas_multiplier: f32, } +impl NymdClient { + pub fn with_mixnet_contract_address(mut self, address: AccountId) -> Self { + self.mixnet_contract_address = address; + self + } + pub fn with_vesting_contract_address(mut self, address: AccountId) -> Self { + self.vesting_contract_address = address; + self + } + pub fn with_bandwidth_claim_contract_address(mut self, address: AccountId) -> Self { + self.bandwidth_claim_contract_address = address; + self + } + pub fn with_coconut_bandwidth_contract_address(mut self, address: AccountId) -> Self { + self.coconut_bandwidth_contract_address = address; + self + } + pub fn with_multisig_contract_address(mut self, address: AccountId) -> Self { + self.multisig_contract_address = address; + self + } +} + impl NymdClient { - pub fn connect( - endpoint: U, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, - ) -> Result, NymdError> + pub fn connect(endpoint: U) -> Result, NymdError> where U: TryInto, { Ok(NymdClient { client: QueryNymdClient::new(endpoint)?, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, client_address: None, simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), + coconut_bandwidth_contract_address: DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .parse() + .unwrap(), + multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(), }) } } @@ -91,9 +126,6 @@ impl NymdClient { pub fn connect_with_signer( network: config::defaults::all::Network, endpoint: U, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, signer: DirectSecp256k1HdWallet, gas_price: Option, ) -> Result, NymdError> @@ -110,20 +142,31 @@ impl NymdClient { Ok(NymdClient { client: SigningNymdClient::connect_with_signer(endpoint, signer, gas_price)?, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, client_address: Some(client_address), simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), + coconut_bandwidth_contract_address: DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .parse() + .unwrap(), + multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(), }) } pub fn connect_with_mnemonic( network: config::defaults::all::Network, endpoint: U, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, mnemonic: bip39::Mnemonic, gas_price: Option, ) -> Result, NymdError> @@ -142,32 +185,48 @@ impl NymdClient { Ok(NymdClient { client: SigningNymdClient::connect_with_signer(endpoint, wallet, gas_price)?, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, client_address: Some(client_address), simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), + coconut_bandwidth_contract_address: DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .parse() + .unwrap(), + multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(), }) } } impl NymdClient { - pub fn mixnet_contract_address(&self) -> Result<&AccountId, NymdError> { - self.mixnet_contract_address - .as_ref() - .ok_or(NymdError::NoContractAddressAvailable) + pub fn mixnet_contract_address(&self) -> &AccountId { + &self.mixnet_contract_address } - pub fn vesting_contract_address(&self) -> Result<&AccountId, NymdError> { - self.vesting_contract_address - .as_ref() - .ok_or(NymdError::NoContractAddressAvailable) + pub fn vesting_contract_address(&self) -> &AccountId { + &self.vesting_contract_address } - pub fn erc20_bridge_contract_address(&self) -> Result<&AccountId, NymdError> { - self.erc20_bridge_contract_address - .as_ref() - .ok_or(NymdError::NoContractAddressAvailable) + pub fn bandwidth_claim_contract_address(&self) -> &AccountId { + &self.bandwidth_claim_contract_address + } + + pub fn coconut_bandwidth_contract_address(&self) -> &AccountId { + &self.coconut_bandwidth_contract_address + } + + pub fn multisig_contract_address(&self) -> &AccountId { + &self.multisig_contract_address } pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) { @@ -295,7 +354,7 @@ impl NymdClient { { let request = QueryMsg::StateParams {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -305,7 +364,7 @@ impl NymdClient { { let request = QueryMsg::QueryOperatorReward { address }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -324,7 +383,7 @@ impl NymdClient { proxy, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -341,7 +400,7 @@ impl NymdClient { proxy_address, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -351,7 +410,7 @@ impl NymdClient { { let request = QueryMsg::GetCurrentEpoch {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -361,7 +420,7 @@ impl NymdClient { { let request = QueryMsg::GetContractVersion {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -378,7 +437,7 @@ impl NymdClient { interval_id, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -388,7 +447,7 @@ impl NymdClient { { let request = QueryMsg::GetCurrentRewardedSetHeight {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -400,7 +459,7 @@ impl NymdClient { { let request = QueryMsg::GetRewardedSetUpdateDetails {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -420,7 +479,7 @@ impl NymdClient { }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -430,7 +489,7 @@ impl NymdClient { { let request = QueryMsg::LayerDistribution {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -440,7 +499,7 @@ impl NymdClient { { let request = QueryMsg::GetRewardPool {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -450,7 +509,7 @@ impl NymdClient { { let request = QueryMsg::GetCirculatingSupply {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -460,7 +519,7 @@ impl NymdClient { { let request = QueryMsg::GetSybilResistancePercent {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -470,7 +529,7 @@ impl NymdClient { { let request = QueryMsg::GetActiveSetWorkFactor {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -480,7 +539,7 @@ impl NymdClient { { let request = QueryMsg::GetIntervalRewardPercent {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -490,7 +549,7 @@ impl NymdClient { { let request = QueryMsg::GetEpochsInInterval {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -504,7 +563,7 @@ impl NymdClient { }; let response: MixOwnershipResponse = self .client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await?; Ok(response.mixnode) } @@ -519,7 +578,7 @@ impl NymdClient { }; let response: GatewayOwnershipResponse = self .client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await?; Ok(response.gateway) } @@ -537,7 +596,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -554,7 +613,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -574,7 +633,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -594,7 +653,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -614,7 +673,7 @@ impl NymdClient { proxy, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -801,7 +860,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::CompoundOperatorReward", @@ -819,7 +878,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::ClaimOperatorReward", @@ -841,7 +900,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::CompoundDelegatorReward", @@ -863,7 +922,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::ClaimDelegatorReward", @@ -892,7 +951,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding mixnode from rust!", @@ -923,7 +982,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding mixnode on behalf from rust!", @@ -960,7 +1019,7 @@ impl NymdClient { self.client .execute_multiple( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), reqs, fee, "Bonding multiple mixnodes on behalf from rust!", @@ -979,7 +1038,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding mixnode from rust!", @@ -1003,7 +1062,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding mixnode on behalf from rust!", @@ -1029,7 +1088,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Updating mixnode configuration from rust!", @@ -1056,7 +1115,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Delegating to mixnode from rust!", @@ -1086,7 +1145,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Delegating to mixnode on behalf from rust!", @@ -1122,7 +1181,7 @@ impl NymdClient { self.client .execute_multiple( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), reqs, fee, "Delegating to multiple mixnodes on behalf from rust!", @@ -1147,7 +1206,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Removing mixnode delegation from rust!", @@ -1175,7 +1234,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Removing mixnode delegation on behalf from rust!", @@ -1204,7 +1263,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding gateway from rust!", @@ -1235,7 +1294,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding gateway on behalf from rust!", @@ -1272,7 +1331,7 @@ impl NymdClient { self.client .execute_multiple( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), reqs, fee, "Bonding multiple gateways on behalf from rust!", @@ -1291,7 +1350,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding gateway from rust!", @@ -1316,7 +1375,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding gateway on behalf from rust!", @@ -1339,7 +1398,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Updating contract state from rust!", @@ -1358,7 +1417,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Advance current epoch", @@ -1377,7 +1436,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Reconciling delegation events", @@ -1396,7 +1455,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Snapshotting mixnodes", @@ -1423,7 +1482,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Writing rewarded set", diff --git a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs new file mode 100644 index 0000000000..352ee6df26 --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs @@ -0,0 +1,49 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; +use crate::nymd::cosmwasm_client::types::ExecuteResult; +use crate::nymd::error::NymdError; +use crate::nymd::{Coin, Fee, NymdClient}; +use coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg}; + +use async_trait::async_trait; + +#[async_trait] +pub trait CoconutBandwidthSigningClient { + async fn deposit( + &self, + amount: Coin, + info: String, + verification_key: String, + encryption_key: String, + fee: Option, + ) -> Result; +} + +#[async_trait] +impl CoconutBandwidthSigningClient for NymdClient { + async fn deposit( + &self, + amount: Coin, + info: String, + verification_key: String, + encryption_key: String, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::DepositFunds { + data: DepositData::new(info.to_string(), verification_key, encryption_key), + }; + self.client + .execute( + self.address(), + self.coconut_bandwidth_contract_address(), + &req, + fee, + "CoconutBandwidth::Deposit", + vec![amount], + ) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/mod.rs b/common/client-libs/validator-client/src/nymd/traits/mod.rs index 20834c9f86..9198522d2d 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mod.rs +++ b/common/client-libs/validator-client/src/nymd/traits/mod.rs @@ -1,8 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +mod coconut_bandwidth_signing_client; +mod multisig_query_client; +mod multisig_signing_client; mod vesting_query_client; mod vesting_signing_client; +pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient; +pub use multisig_query_client::QueryClient; +pub use multisig_signing_client::MultisigSigningClient; pub use vesting_query_client::VestingQueryClient; pub use vesting_signing_client::VestingSigningClient; diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs new file mode 100644 index 0000000000..5869ada26a --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs @@ -0,0 +1,24 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::nymd::error::NymdError; +use crate::nymd::{CosmWasmClient, NymdClient}; + +use multisig_contract_common::msg::{ProposalResponse, QueryMsg}; + +use async_trait::async_trait; + +#[async_trait] +pub trait QueryClient { + async fn get_proposal(&self, proposal_id: u64) -> Result; +} + +#[async_trait] +impl QueryClient for NymdClient { + async fn get_proposal(&self, proposal_id: u64) -> Result { + let request = QueryMsg::Proposal { proposal_id }; + self.client + .query_contract_smart(self.multisig_contract_address(), &request) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs new file mode 100644 index 0000000000..0d9fca57e3 --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs @@ -0,0 +1,116 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +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 coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; +use multisig_contract_common::msg::ExecuteMsg; + +use async_trait::async_trait; +use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg}; +use cw3::Vote; +use network_defaults::DEFAULT_NETWORK; + +#[async_trait] +pub trait MultisigSigningClient { + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result; + + async fn vote_proposal( + &self, + proposal_id: u64, + yes: bool, + fee: Option, + ) -> Result; + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> Result; +} + +#[async_trait] +impl MultisigSigningClient for NymdClient { + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds { + funds: Coin::new(voucher_value, DEFAULT_NETWORK.denom()), + }; + let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.coconut_bandwidth_contract_address().to_string(), + msg: to_binary(&release_funds_req)?, + funds: vec![], + }); + let req = ExecuteMsg::Propose { + title, + description: blinded_serial_number, + msgs: vec![release_funds_msg], + latest: None, + }; + self.client + .execute( + self.address(), + self.multisig_contract_address(), + &req, + fee, + "Multisig::Propose::Execute::ReleaseFunds", + vec![], + ) + .await + } + + async fn vote_proposal( + &self, + proposal_id: u64, + vote_yes: bool, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let vote = if vote_yes { Vote::Yes } else { Vote::No }; + let req = ExecuteMsg::Vote { proposal_id, vote }; + self.client + .execute( + self.address(), + self.multisig_contract_address(), + &req, + fee, + "Multisig::Vote", + vec![], + ) + .await + } + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::Execute { proposal_id }; + self.client + .execute( + self.address(), + self.multisig_contract_address(), + &req, + fee, + "Multisig::Execute", + vec![], + ) + .await + } +} 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 a020dea2ea..ea20cb6654 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 @@ -84,7 +84,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -99,7 +99,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -113,7 +113,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -127,7 +127,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -140,7 +140,7 @@ impl VestingQueryClient for NymdClient { vesting_account_address: vesting_account_address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -152,7 +152,7 @@ impl VestingQueryClient for NymdClient { vesting_account_address: vesting_account_address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -164,7 +164,7 @@ impl VestingQueryClient for NymdClient { vesting_account_address: vesting_account_address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -178,7 +178,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -193,7 +193,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -203,7 +203,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } async fn get_mixnode_pledge(&self, address: &str) -> Result, NymdError> { @@ -211,7 +211,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } async fn get_gateway_pledge(&self, address: &str) -> Result, NymdError> { @@ -219,7 +219,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -228,7 +228,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .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 163b6957db..e24473e81d 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 @@ -129,7 +129,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UpdateMixnetConfig", @@ -150,7 +150,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UpdateMixnetAddress", @@ -175,7 +175,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::BondGateway", @@ -190,7 +190,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UnbondGateway", @@ -213,7 +213,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::TrackUnbondGateway", @@ -238,7 +238,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::BondMixnode", @@ -253,7 +253,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UnbondMixnode", @@ -276,7 +276,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::TrackUnbondMixnode", @@ -296,7 +296,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::WithdrawVested", @@ -320,7 +320,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::TrackUndelegation", @@ -342,7 +342,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::DelegateToMixnode", @@ -363,7 +363,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UndelegateFromMixnode", @@ -389,7 +389,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::CreatePeriodicVestingAccount", @@ -407,7 +407,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::ClaimOperatorReward", @@ -425,7 +425,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::CompoundOperatorReward", @@ -444,7 +444,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::ClaimDelegatorReward", @@ -463,7 +463,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::CompoundDelegatorReward", diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 66c86f55b6..be0ad92727 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -3,7 +3,11 @@ use crate::validator_api::error::ValidatorAPIError; use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; -use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; +use coconut_interface::{ + BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody, + ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse, + VerifyCredentialBody, VerifyCredentialResponse, +}; use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -371,6 +375,57 @@ impl Client { ) .await } + + pub async fn verify_bandwidth_credential( + &self, + request_body: &VerifyCredentialBody, + ) -> Result { + self.post_validator_api( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, + ], + NO_PARAMS, + request_body, + ) + .await + } + + pub async fn propose_release_funds( + &self, + request_body: &ProposeReleaseFundsRequestBody, + ) -> Result { + self.post_validator_api( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_PROPOSE_RELEASE_FUNDS, + ], + NO_PARAMS, + request_body, + ) + .await + } + + pub async fn execute_release_funds( + &self, + request_body: &ExecuteReleaseFundsRequestBody, + ) -> Result<(), ValidatorAPIError> { + self.post_validator_api( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_EXECUTE_RELEASE_FUNDS, + ], + NO_PARAMS, + request_body, + ) + .await + } } // utility function that should solve the double slash problem in validator API forever. diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index 61c4e309e8..b6939d18ab 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -17,6 +17,9 @@ pub const BANDWIDTH: &str = "bandwidth"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential"; pub const COCONUT_VERIFICATION_KEY: &str = "verification-key"; +pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; +pub const COCONUT_PROPOSE_RELEASE_FUNDS: &str = "propose-release-funds"; +pub const COCONUT_EXECUTE_RELEASE_FUNDS: &str = "execute-release-funds"; pub const STATUS_ROUTES: &str = "status"; pub const MIXNODE: &str = "mixnode"; diff --git a/common/coconut-interface/src/error.rs b/common/coconut-interface/src/error.rs index 95ba31db88..51b047d0d5 100644 --- a/common/coconut-interface/src/error.rs +++ b/common/coconut-interface/src/error.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nymcoconut::CoconutError; use thiserror::Error; #[derive(Debug, Error)] @@ -11,9 +12,6 @@ pub enum CoconutInterfaceError { #[error("Could not decode base 58 string - {0}")] MalformedString(#[from] bs58::decode::Error), - #[error("Not enough public attributes were specified")] - NotEnoughPublicAttributes, - - #[error("Could not recover bandwidth value")] - InvalidBandwidth, + #[error("Coconut error - {0}")] + CoconutError(#[from] CoconutError), } diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 1e8d726f48..8cb345dbf5 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -5,96 +5,158 @@ pub mod error; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use std::str::FromStr; use error::CoconutInterfaceError; pub use nymcoconut::*; -#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)] +#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq)] pub struct Credential { #[getset(get = "pub")] n_params: u32, #[getset(get = "pub")] theta: Theta, - public_attributes: Vec>, - #[getset(get = "pub")] - signature: Signature, + voucher_value: u64, + voucher_info: String, } impl Credential { pub fn new( n_params: u32, theta: Theta, - voucher_value: String, + voucher_value: u64, voucher_info: String, - signature: &Signature, ) -> Credential { - let public_attributes = vec![voucher_value.into_bytes(), voucher_info.into_bytes()]; Credential { n_params, theta, - public_attributes, - signature: *signature, + voucher_value, + voucher_info, } } - pub fn voucher_value(&self) -> Result { - let bandwidth_vec = self - .public_attributes - .get(0) - .ok_or(CoconutInterfaceError::NotEnoughPublicAttributes)? - .to_owned(); - let bandwidth_str = String::from_utf8(bandwidth_vec) - .map_err(|_| CoconutInterfaceError::InvalidBandwidth)?; - let value = - u64::from_str(&bandwidth_str).map_err(|_| CoconutInterfaceError::InvalidBandwidth)?; + pub fn blinded_serial_number(&self) -> String { + self.theta.blinded_serial_number_bs58() + } - Ok(value) + pub fn has_blinded_serial_number( + &self, + blinded_serial_number_bs58: &str, + ) -> Result { + Ok(self + .theta + .has_blinded_serial_number(blinded_serial_number_bs58)?) + } + + pub fn voucher_value(&self) -> u64 { + self.voucher_value } pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); - let public_attributes = self - .public_attributes - .iter() - .map(hash_to_scalar) - .collect::>(); + let public_attributes = vec![ + self.voucher_value.to_string().as_bytes(), + self.voucher_info.as_bytes(), + ] + .iter() + .map(hash_to_scalar) + .collect::>(); nymcoconut::verify_credential(¶ms, verification_key, &self.theta, &public_attributes) } + + pub fn as_bytes(&self) -> Vec { + let n_params_bytes = self.n_params.to_be_bytes(); + let theta_bytes = self.theta.to_bytes(); + let theta_bytes_len = theta_bytes.len(); + let voucher_value_bytes = self.voucher_value.to_be_bytes(); + let voucher_info_bytes = self.voucher_info.as_bytes(); + let voucher_info_len = voucher_info_bytes.len(); + + let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len); + bytes.extend_from_slice(&n_params_bytes); + bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes()); + bytes.extend_from_slice(&theta_bytes); + bytes.extend_from_slice(&voucher_value_bytes); + bytes.extend_from_slice(voucher_info_bytes); + + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 28 { + return Err(CoconutError::Deserialization(String::from( + "To few bytes in credential", + ))); + } + let mut four_byte = [0u8; 4]; + let mut eight_byte = [0u8; 8]; + + four_byte.copy_from_slice(&bytes[..4]); + let n_params = u32::from_be_bytes(four_byte); + eight_byte.copy_from_slice(&bytes[4..12]); + let theta_len = u64::from_be_bytes(eight_byte); + if bytes.len() < 28 + theta_len as usize { + return Err(CoconutError::Deserialization(String::from( + "To few bytes in credential", + ))); + } + let theta = Theta::from_bytes(&bytes[12..12 + theta_len as usize]) + .map_err(|e| CoconutError::Deserialization(e.to_string()))?; + eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]); + let voucher_value = u64::from_be_bytes(eight_byte); + let voucher_info = String::from_utf8(bytes[20 + theta_len as usize..].to_vec()) + .map_err(|e| CoconutError::Deserialization(e.to_string()))?; + + Ok(Credential { + n_params, + theta, + voucher_value, + voucher_info, + }) + } } -#[derive(Serialize, Deserialize, Debug, Getters, CopyGetters)] +impl Bytable for Credential { + fn to_byte_vec(&self) -> Vec { + self.as_bytes() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + Credential::from_bytes(slice) + } +} + +impl Base58 for Credential {} + +#[derive(Serialize, Deserialize, Getters, CopyGetters)] pub struct VerifyCredentialBody { #[getset(get = "pub")] - n_params: u32, + credential: Credential, #[getset(get = "pub")] - theta: Theta, - public_attributes: Vec, + proposal_id: u64, } impl VerifyCredentialBody { - pub fn new( - n_params: u32, - theta: &Theta, - public_attributes: &[Attribute], - ) -> VerifyCredentialBody { + pub fn new(credential: Credential, proposal_id: u64) -> VerifyCredentialBody { VerifyCredentialBody { - n_params, - theta: theta.clone(), - public_attributes: public_attributes - .iter() - .map(|attr| attr.to_bs58()) - .collect(), + credential, + proposal_id, } } +} - pub fn public_attributes(&self) -> Vec { - self.public_attributes - .iter() - .map(|x| Attribute::try_from_bs58(x).unwrap()) - .collect() +#[derive(Debug, Serialize, Deserialize)] +pub struct VerifyCredentialResponse { + pub verification_result: bool, +} + +impl VerifyCredentialResponse { + pub fn new(verification_result: bool) -> Self { + VerifyCredentialResponse { + verification_result, + } } } + // All strings are base58 encoded representations of structs #[derive(Clone, Serialize, Deserialize, Debug, Getters, CopyGetters)] pub struct BlindSignRequestBody { @@ -194,3 +256,86 @@ impl VerificationKeyResponse { VerificationKeyResponse { key } } } + +#[derive(Serialize, Deserialize, Getters, CopyGetters)] +pub struct ProposeReleaseFundsRequestBody { + #[getset(get = "pub")] + credential: Credential, +} + +impl ProposeReleaseFundsRequestBody { + pub fn new(credential: Credential) -> Self { + ProposeReleaseFundsRequestBody { credential } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ProposeReleaseFundsResponse { + pub proposal_id: u64, +} + +impl ProposeReleaseFundsResponse { + pub fn new(proposal_id: u64) -> Self { + ProposeReleaseFundsResponse { proposal_id } + } +} + +#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters)] +pub struct ExecuteReleaseFundsRequestBody { + #[getset(get = "pub")] + proposal_id: u64, +} + +impl ExecuteReleaseFundsRequestBody { + pub fn new(proposal_id: u64) -> Self { + ExecuteReleaseFundsRequestBody { proposal_id } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde_coconut_credential() { + let voucher_value = 1000000u64; + let voucher_info = String::from("BandwidthVoucher"); + let serial_number = + Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap(); + let binding_number = + Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap(); + let signature = Signature::try_from_bs58( + "ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\ + 7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\ + ZR9j", + ) + .unwrap(); + let params = Parameters::new(4).unwrap(); + let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\ + woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\ + FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\ + JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\ + UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\ + wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\ + 1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\ + nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\ + ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\ + 6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\ + Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y") + .unwrap(); + let theta = prove_bandwidth_credential( + ¶ms, + &verification_key, + &signature, + serial_number, + binding_number, + ) + .unwrap(); + let credential = Credential::new(4, theta, voucher_value, voucher_info); + + let serialized_credential = credential.as_bytes(); + let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap(); + + assert_eq!(credential, deserialized_credential); + } +} diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml new file mode 100644 index 0000000000..c7437e4a6d --- /dev/null +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "multisig-contract-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cw-utils = { version = "0.13.1" } +cw3 = { version = "0.13.1" } +cw4 = { version = "0.13.1" } +cosmwasm-std = "1.0.0-beta6" +schemars = "0.8" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs new file mode 100644 index 0000000000..d0e87a0d30 --- /dev/null +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs @@ -0,0 +1 @@ +pub mod msg; diff --git a/contracts/multisig/cw3-flex-multisig/src/msg.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs similarity index 94% rename from contracts/multisig/cw3-flex-multisig/src/msg.rs rename to common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs index 8d72cd37b0..19b440fa76 100644 --- a/contracts/multisig/cw3-flex-multisig/src/msg.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs @@ -1,7 +1,11 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{CosmosMsg, Empty}; +pub use cw3::ProposalResponse; use cw3::Vote; use cw4::MemberChangedHookMsg; use cw_utils::{Duration, Expiration, Threshold}; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 282614238e..0ffee7abdc 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -51,12 +51,7 @@ pub async fn obtain_aggregate_verification_key( let mut shares = Vec::with_capacity(validators.len()); let mut client = validator_client::ApiClient::new(validators[0].clone()); - let response = client.get_coconut_verification_key().await?; - - indices.push(1); - shares.push(response.key); - - for (id, validator_url) in validators.iter().enumerate().skip(1) { + for (id, validator_url) in validators.iter().enumerate() { client.change_validator_api(validator_url.clone()); let response = client.get_coconut_verification_key().await?; indices.push((id + 1) as u64); @@ -135,14 +130,7 @@ pub async fn obtain_aggregate_signature( let mut validators_partial_vks: Vec = Vec::with_capacity(validators.len()); let mut client = validator_client::ApiClient::new(validators[0].clone()); - let validator_partial_vk = client.get_coconut_verification_key().await?; - validators_partial_vks.push(validator_partial_vk.key.clone()); - - let first = - obtain_partial_credential(params, attributes, &client, &validator_partial_vk.key).await?; - shares.push(SignatureShare::new(first, 1)); - - for (id, validator_url) in validators.iter().enumerate().skip(1) { + for (id, validator_url) in validators.iter().enumerate() { client.change_validator_api(validator_url.clone()); let validator_partial_vk = client.get_coconut_verification_key().await?; validators_partial_vks.push(validator_partial_vk.key.clone()); @@ -193,8 +181,7 @@ pub fn prepare_credential_for_spending( Ok(Credential::new( PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES, theta, - voucher_value.to_string(), + voucher_value, voucher_info, - signature, )) } diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 8f628ac863..86b9abc140 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(feature = "coconut")] -use coconut_interface::{error::CoconutInterfaceError, CoconutError}; +use coconut_interface::CoconutError; use crypto::asymmetric::encryption::KeyRecoveryError; use validator_client::ValidatorClientError; @@ -20,10 +20,6 @@ pub enum Error { #[error("Ran into a coconut error - {0}")] CoconutError(#[from] CoconutError), - #[cfg(feature = "coconut")] - #[error("Ran into a coconut interface error - {0}")] - CoconutInterfaceError(#[from] CoconutInterfaceError), - #[error("Ran into a validator client error - {0}")] ValidatorClientError(#[from] ValidatorClientError), diff --git a/common/network-defaults/src/all.rs b/common/network-defaults/src/all.rs index a7e4ea58d8..bdc62fe952 100644 --- a/common/network-defaults/src/all.rs +++ b/common/network-defaults/src/all.rs @@ -52,6 +52,14 @@ impl Network { self.details().bandwidth_claim_contract_address } + pub fn coconut_bandwidth_contract_address(&self) -> &str { + self.details().coconut_bandwidth_contract_address + } + + pub fn multisig_contract_address(&self) -> &str { + self.details().multisig_contract_address + } + pub fn rewarding_validator_address(&self) -> &str { self.details().rewarding_validator_address } diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index a992d5bed6..66c8621843 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -51,6 +51,8 @@ pub struct DefaultNetworkDetails<'a> { mixnet_contract_address: &'a str, vesting_contract_address: &'a str, bandwidth_claim_contract_address: &'a str, + coconut_bandwidth_contract_address: &'a str, + multisig_contract_address: &'a str, rewarding_validator_address: &'a str, validators: Vec, } @@ -62,6 +64,8 @@ static MAINNET_DEFAULTS: Lazy> = mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS, vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS, bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS, rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS, validators: mainnet::validators(), }); @@ -73,6 +77,8 @@ static SANDBOX_DEFAULTS: Lazy> = mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS, vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS, bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS, rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS, validators: sandbox::validators(), }); @@ -83,6 +89,8 @@ static QA_DEFAULTS: Lazy> = Lazy::new(|| DefaultN mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS, vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS, bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS, rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS, validators: qa::validators(), }); @@ -146,7 +154,7 @@ pub const ETH_ERC20_APPROVE_FUNCTION_NAME: &str = "approve"; // Ethereum constants used for token bridge /// How much bandwidth (in bytes) one token can buy -const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024; +pub const BYTES_PER_UTOKEN: u64 = 1024; /// Threshold for claiming more bandwidth: 1 MB pub const REMAINING_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024; @@ -155,7 +163,7 @@ pub const TOKENS_TO_BURN: u64 = 1; /// How many ERC20 utokens should be burned to buy bandwidth pub const UTOKENS_TO_BURN: u64 = TOKENS_TO_BURN * 1000000; /// Default bandwidth (in bytes) that we try to buy -pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN; +pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN; pub const VOUCHER_INFO: &str = "BandwidthVoucher"; diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 69f34c92a2..0e9f16eb05 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -13,6 +13,9 @@ pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("0000000000000000000000000000000000000000"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = diff --git a/common/network-defaults/src/qa.rs b/common/network-defaults/src/qa.rs index 38ada60537..c782e089ab 100644 --- a/common/network-defaults/src/qa.rs +++ b/common/network-defaults/src/qa.rs @@ -13,6 +13,9 @@ pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav"; pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw"; +pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("0000000000000000000000000000000000000000"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = diff --git a/common/network-defaults/src/sandbox.rs b/common/network-defaults/src/sandbox.rs index 3ec1f746a9..a6cfc39514 100644 --- a/common/network-defaults/src/sandbox.rs +++ b/common/network-defaults/src/sandbox.rs @@ -11,6 +11,9 @@ pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2 pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh"; pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; +pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r"; +pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = diff --git a/common/nymcoconut/src/proofs/mod.rs b/common/nymcoconut/src/proofs/mod.rs index fb43a1d850..d38f5a05cf 100644 --- a/common/nymcoconut/src/proofs/mod.rs +++ b/common/nymcoconut/src/proofs/mod.rs @@ -327,8 +327,7 @@ impl ProofCmCs { } } -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug, PartialEq)] pub struct ProofKappaZeta { // c challenge: Scalar, diff --git a/common/nymcoconut/src/scheme/double_use.rs b/common/nymcoconut/src/scheme/double_use.rs new file mode 100644 index 0000000000..f952938a72 --- /dev/null +++ b/common/nymcoconut/src/scheme/double_use.rs @@ -0,0 +1,49 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bls12_381::G2Projective; +use group::Curve; +use std::convert::TryFrom; +use std::convert::TryInto; + +use crate::error::{CoconutError, Result}; +use crate::traits::{Base58, Bytable}; +use crate::utils::try_deserialize_g2_projective; + +pub struct BlindedSerialNumber { + pub(crate) inner: G2Projective, +} + +impl TryFrom<&[u8]> for BlindedSerialNumber { + type Error = CoconutError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != 96 { + return Err( + CoconutError::Deserialization( + format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected 96, got {}", bytes.len()), + )); + } + + let inner = try_deserialize_g2_projective( + &bytes.try_into().unwrap(), + CoconutError::Deserialization( + "failed to deserialize the blinded serial number (zeta)".to_string(), + ), + )?; + + Ok(BlindedSerialNumber { inner }) + } +} + +impl Bytable for BlindedSerialNumber { + fn to_byte_vec(&self) -> Vec { + self.inner.to_affine().to_compressed().to_vec() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + Self::try_from(slice) + } +} + +impl Base58 for BlindedSerialNumber {} diff --git a/common/nymcoconut/src/scheme/mod.rs b/common/nymcoconut/src/scheme/mod.rs index e85830cada..881ac0ec53 100644 --- a/common/nymcoconut/src/scheme/mod.rs +++ b/common/nymcoconut/src/scheme/mod.rs @@ -19,6 +19,7 @@ use crate::utils::try_deserialize_g1_projective; use crate::Attribute; pub mod aggregation; +pub mod double_use; pub mod issuance; pub mod keygen; pub mod setup; @@ -27,8 +28,7 @@ pub mod verification; pub type SignerIndex = u64; // (h, s) -#[derive(Debug, Clone, Copy)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); pub type PartialSignature = Signature; diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index c2ccd2da12..18e639828a 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -10,6 +10,7 @@ use group::{Curve, Group}; use crate::error::{CoconutError, Result}; use crate::proofs::ProofKappaZeta; +use crate::scheme::double_use::BlindedSerialNumber; use crate::scheme::setup::Parameters; use crate::scheme::Signature; use crate::scheme::VerificationKey; @@ -19,8 +20,7 @@ use crate::Attribute; // TODO NAMING: this whole thing // Theta -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug, PartialEq)] pub struct Theta { // blinded_message (kappa) pub blinded_message: G2Projective, @@ -81,6 +81,12 @@ impl Theta { ) } + pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result { + let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?; + let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner); + Ok(ret) + } + // blinded message (kappa) || blinded serial number (zeta) || credential || pi_v pub fn to_bytes(&self) -> Vec { let blinded_message_bytes = self.blinded_message.to_affine().to_compressed(); @@ -100,6 +106,13 @@ impl Theta { pub fn from_bytes(bytes: &[u8]) -> Result { Theta::try_from(bytes) } + + pub fn blinded_serial_number_bs58(&self) -> String { + let blinded_serial_nuumber = BlindedSerialNumber { + inner: self.blinded_serial_number, + }; + blinded_serial_nuumber.to_bs58() + } } impl Bytable for Theta { diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 0ace19f985..055efa78b3 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -502,6 +502,7 @@ dependencies = [ "cw3-fixed-multisig", "cw4", "cw4-group", + "multisig-contract-common", "schemars", "serde", "thiserror", @@ -1042,6 +1043,18 @@ dependencies = [ "time 0.3.6", ] +[[package]] +name = "multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", +] + [[package]] name = "network-defaults" version = "0.1.0" diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index 86ae1f6450..9c83f0fc21 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -28,6 +28,8 @@ schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } +multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } + [dev-dependencies] cosmwasm-schema = { version = "1.0.0-beta6" } cw4-group = { path = "../cw4-group", version = "0.13.1" } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index 5616cba7dd..0af5001f12 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -18,8 +18,8 @@ use cw_storage_plus::Bound; use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{Config, CONFIG}; +use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig"; @@ -499,7 +499,7 @@ mod tests { max_voting_period: Duration, ) -> Addr { let flex_id = app.store_code(contract_flex()); - let msg = crate::msg::InstantiateMsg { + let msg = InstantiateMsg { group_addr: group.to_string(), threshold, max_voting_period, diff --git a/contracts/multisig/cw3-flex-multisig/src/lib.rs b/contracts/multisig/cw3-flex-multisig/src/lib.rs index e5ff7237ef..e544aa55c8 100644 --- a/contracts/multisig/cw3-flex-multisig/src/lib.rs +++ b/contracts/multisig/cw3-flex-multisig/src/lib.rs @@ -1,6 +1,5 @@ pub mod contract; pub mod error; -pub mod msg; pub mod state; pub use crate::error::ContractError; diff --git a/explorer-api/src/client.rs b/explorer-api/src/client.rs index 8688058fef..b7c628b256 100644 --- a/explorer-api/src/client.rs +++ b/explorer-api/src/client.rs @@ -23,19 +23,10 @@ impl ThreadsafeValidatorClient { } pub(crate) fn new_validator_client() -> ThreadsafeValidatorClient { - let network = DEFAULT_NETWORK; - let mixnet_contract = network.mixnet_contract_address().to_string(); let nymd_url = default_nymd_endpoints()[0].clone(); let api_url = default_api_endpoints()[0].clone(); - let client_config = validator_client::Config::new( - network, - nymd_url, - api_url, - Some(mixnet_contract.parse().unwrap()), - None, - None, - ); + let client_config = validator_client::Config::new(DEFAULT_NETWORK, nymd_url, api_url); ThreadsafeValidatorClient(Arc::new( validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"), diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index 2d0b46dd88..9501755306 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -18,7 +18,6 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -bincode = "1.3" crypto = { path = "../../common/crypto" } pemstore = { path = "../../common/pemstore" } diff --git a/gateway/gateway-requests/src/registration/handshake/error.rs b/gateway/gateway-requests/src/registration/handshake/error.rs index 0bf672d2b5..81b2c2d0c7 100644 --- a/gateway/gateway-requests/src/registration/handshake/error.rs +++ b/gateway/gateway-requests/src/registration/handshake/error.rs @@ -25,9 +25,4 @@ pub enum HandshakeError { MalformedRequest, #[error("sent request was malformed")] HandshakeFailure, - #[error("could not deserialize from slice: {source}")] - DeserializationError { - #[from] - source: bincode::Error, - }, } diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 1043a6075e..5206f444a8 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -146,18 +146,13 @@ impl ClientControlRequest { credential: &Credential, shared_key: &SharedKeys, iv: IV, - ) -> Option { - match bincode::serialize(credential) { - Ok(serialized_credential) => { - let enc_credential = - shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); + ) -> Self { + let serialized_credential = credential.as_bytes(); + let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); - Some(ClientControlRequest::BandwidthCredential { - enc_credential, - iv: iv.to_bytes(), - }) - } - _ => None, + ClientControlRequest::BandwidthCredential { + enc_credential, + iv: iv.to_bytes(), } } @@ -167,8 +162,9 @@ impl ClientControlRequest { shared_key: &SharedKeys, iv: IV, ) -> Result { - let credential = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; - bincode::deserialize(&credential).map_err(|_| GatewayRequestsError::MalformedEncryption) + let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; + Credential::from_bytes(&credential_bytes) + .map_err(|_| GatewayRequestsError::MalformedEncryption) } #[cfg(not(feature = "coconut"))] diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index 28a41568b4..e88b531f83 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,13 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "coconut")] -use std::convert::TryFrom; - #[cfg(feature = "coconut")] use coconut_interface::Credential; -#[cfg(feature = "coconut")] -use credentials::error::Error; #[cfg(not(feature = "coconut"))] use credentials::token::bandwidth::TokenCredential; @@ -22,12 +17,13 @@ impl Bandwidth { } #[cfg(feature = "coconut")] -impl TryFrom for Bandwidth { - type Error = Error; - - fn try_from(credential: Credential) -> Result { - let value = credential.voucher_value()?; - Ok(Self { value }) +impl From for Bandwidth { + fn from(credential: Credential) -> Self { + let token_value = credential.voucher_value(); + let bandwidth_bytes = token_value * network_defaults::BYTES_PER_UTOKEN; + Bandwidth { + value: bandwidth_bytes, + } } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index e636c7a98a..c6ea692b33 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -42,8 +42,8 @@ pub(crate) enum RequestHandlingError { #[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)] UnsupportedBandwidthValue(u64), - #[error("Provided bandwidth credential did not verify correctly")] - InvalidBandwidthCredential, + #[error("Provided bandwidth credential did not verify correctly on {0}")] + InvalidBandwidthCredential(String), #[error("This gateway is not running in the disabled credentials mode")] NotInDisabledCredentialsMode, @@ -65,8 +65,12 @@ pub(crate) enum RequestHandlingError { NymdError(#[from] validator_client::nymd::error::NymdError), #[cfg(feature = "coconut")] - #[error("Provided coconut bandwidth credential did not have expected structure - {0}")] - CoconutBandwidthCredentialError(#[from] credentials::error::Error), + #[error("Validator API error")] + APIError(#[from] validator_client::ValidatorClientError), + + #[cfg(feature = "coconut")] + #[error("Not enough validator API endpoints provided. Needed {needed}, received {received}")] + NotEnoughValidatorAPIs { received: usize, needed: usize }, } impl RequestHandlingError { @@ -208,11 +212,55 @@ where iv, )?; - if !credential.verify(&self.inner.aggregated_verification_key) { - return Err(RequestHandlingError::InvalidBandwidthCredential); + if !credential.verify( + self.inner + .coconut_verifier + .as_ref() + .aggregated_verification_key(), + ) { + return Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("credential failed to verify on gateway"), + )); } - let bandwidth = Bandwidth::try_from(credential)?; + let req = coconut_interface::ProposeReleaseFundsRequestBody::new(credential.clone()); + let proposal_id = self + .inner + .coconut_verifier + .api_clients() + .get(0) + .ok_or(RequestHandlingError::NotEnoughValidatorAPIs { + needed: 1, + received: 0, + })? + .propose_release_funds(&req) + .await? + .proposal_id; + + let req = coconut_interface::VerifyCredentialBody::new(credential.clone(), proposal_id); + for client in self.inner.coconut_verifier.api_clients().iter().skip(1) { + if !client + .verify_bandwidth_credential(&req) + .await? + .verification_result + { + debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.validator_api.current_url()); + } + } + + let req = coconut_interface::ExecuteReleaseFundsRequestBody::new(proposal_id); + self.inner + .coconut_verifier + .api_clients() + .get(0) + .ok_or(RequestHandlingError::NotEnoughValidatorAPIs { + needed: 1, + received: 0, + })? + .execute_release_funds(&req) + .await?; + + let bandwidth = Bandwidth::from(credential); let bandwidth_value = bandwidth.value(); if bandwidth_value > i64::MAX as u64 { @@ -254,11 +302,15 @@ where .inner .check_local_identity(&credential.gateway_identity()) { - return Err(RequestHandlingError::InvalidBandwidthCredential); + return Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )); } if !credential.verify_signature() { - return Err(RequestHandlingError::InvalidBandwidthCredential); + return Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )); } debug!("Verifying Ethereum for token burn..."); let gateway_owner = self diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs new file mode 100644 index 0000000000..deae5479da --- /dev/null +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -0,0 +1,27 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use coconut_interface::VerificationKey; +use validator_client::ApiClient; + +pub struct CoconutVerifier { + api_clients: Vec, + aggregated_verification_key: VerificationKey, +} + +impl CoconutVerifier { + pub fn new(api_clients: Vec, aggregated_verification_key: VerificationKey) -> Self { + CoconutVerifier { + api_clients, + aggregated_verification_key, + } + } + + pub fn api_clients(&self) -> &Vec { + &self.api_clients + } + + pub fn aggregated_verification_key(&self) -> &VerificationKey { + &self.aggregated_verification_key + } +} diff --git a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs index 2bb81aefcd..d090a496ce 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs @@ -39,16 +39,9 @@ impl ERC20Bridge { .expect("The list of validators is empty"); let mnemonic = Mnemonic::from_str(&cosmos_mnemonic).expect("Invalid Cosmos mnemonic provided"); - let nymd_client = NymdClient::connect_with_mnemonic( - DEFAULT_NETWORK, - nymd_url.as_ref(), - AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).ok(), - None, - AccountId::from_str(DEFAULT_NETWORK.bandwidth_claim_contract_address()).ok(), - mnemonic, - None, - ) - .expect("Could not create nymd client"); + let nymd_client = + NymdClient::connect_with_mnemonic(DEFAULT_NETWORK, nymd_url.as_ref(), mnemonic, None) + .expect("Could not create nymd client"); ERC20Bridge { contract: eth_contract(web3.clone()), @@ -95,7 +88,9 @@ impl ERC20Bridge { } } - Err(RequestHandlingError::InvalidBandwidthCredential) + Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )) } pub(crate) async fn verify_gateway_owner( @@ -103,17 +98,22 @@ impl ERC20Bridge { gateway_owner: String, gateway_identity: &PublicKey, ) -> Result<(), RequestHandlingError> { - let owner_address = AccountId::from_str(&gateway_owner) - .map_err(|_| RequestHandlingError::InvalidBandwidthCredential)?; + let owner_address = AccountId::from_str(&gateway_owner).map_err(|_| { + RequestHandlingError::InvalidBandwidthCredential(String::from("gateway")) + })?; let gateway_bond = self .nymd_client .owns_gateway(&owner_address) .await? - .ok_or(RequestHandlingError::InvalidBandwidthCredential)?; + .ok_or_else(|| { + RequestHandlingError::InvalidBandwidthCredential(String::from("gateway")) + })?; if gateway_bond.gateway.identity_key == gateway_identity.to_base58_string() { Ok(()) } else { - Err(RequestHandlingError::InvalidBandwidthCredential) + Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )) } } @@ -121,9 +121,7 @@ impl ERC20Bridge { &self, credential: &TokenCredential, ) -> Result<(), RequestHandlingError> { - // It's ok to unwrap here, as the cosmos contract is set correctly - let erc20_bridge_contract_address = - self.nymd_client.erc20_bridge_contract_address().unwrap(); + let bandwidth_claim_contract_address = self.nymd_client.bandwidth_claim_contract_address(); let req = ExecuteMsg::LinkPayment { data: LinkPaymentData::new( credential.verification_key().to_bytes(), @@ -134,7 +132,7 @@ impl ERC20Bridge { }; self.nymd_client .execute( - erc20_bridge_contract_address, + bandwidth_claim_contract_address, &req, Default::default(), "Linking payment", diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 2fc83e9e24..8b33a807d2 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::client_handling::active_clients::ActiveClientsStore; +#[cfg(feature = "coconut")] +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; use crate::node::client_handling::websocket::connection_handler::{ AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream, }; @@ -27,9 +29,6 @@ use thiserror::Error; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; -#[cfg(feature = "coconut")] -use coconut_interface::VerificationKey; - #[cfg(not(feature = "coconut"))] use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge; @@ -77,11 +76,10 @@ pub(crate) struct FreshHandler { pub(crate) socket_connection: SocketStream, pub(crate) storage: St, - #[cfg(feature = "coconut")] - pub(crate) aggregated_verification_key: VerificationKey, - #[cfg(not(feature = "coconut"))] pub(crate) erc20_bridge: Arc, + #[cfg(feature = "coconut")] + pub(crate) coconut_verifier: Arc, } impl FreshHandler @@ -102,7 +100,7 @@ where local_identity: Arc, storage: St, active_clients_store: ActiveClientsStore, - #[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey, + #[cfg(feature = "coconut")] coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: Arc, ) -> Self { FreshHandler { @@ -114,7 +112,7 @@ where local_identity, storage, #[cfg(feature = "coconut")] - aggregated_verification_key, + coconut_verifier, #[cfg(not(feature = "coconut"))] erc20_bridge, } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index fdd90e3aea..9172f9288f 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -15,6 +15,8 @@ pub(crate) use self::authenticated::AuthenticatedHandler; pub(crate) use self::fresh::FreshHandler; mod authenticated; +#[cfg(feature = "coconut")] +pub(crate) mod coconut; #[cfg(not(feature = "coconut"))] pub(crate) mod eth_events; mod fresh; diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 3669882cb6..08bf22267a 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -14,7 +14,7 @@ use std::sync::Arc; use tokio::task::JoinHandle; #[cfg(feature = "coconut")] -use coconut_interface::VerificationKey; +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; #[cfg(not(feature = "coconut"))] use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge; @@ -25,8 +25,7 @@ pub(crate) struct Listener { disabled_credentials_mode: bool, #[cfg(feature = "coconut")] - aggregated_verification_key: VerificationKey, - + pub(crate) coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: Arc, } @@ -36,7 +35,7 @@ impl Listener { address: SocketAddr, local_identity: Arc, disabled_credentials_mode: bool, - #[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey, + #[cfg(feature = "coconut")] coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge, ) -> Self { Listener { @@ -44,7 +43,7 @@ impl Listener { local_identity, disabled_credentials_mode, #[cfg(feature = "coconut")] - aggregated_verification_key, + coconut_verifier, #[cfg(not(feature = "coconut"))] erc20_bridge: Arc::new(erc20_bridge), } @@ -84,7 +83,7 @@ impl Listener { storage.clone(), active_clients_store.clone(), #[cfg(feature = "coconut")] - self.aggregated_verification_key.clone(), + Arc::clone(&self.coconut_verifier), #[cfg(not(feature = "coconut"))] Arc::clone(&self.erc20_bridge), ); diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index a14dc90074..5b33bdb319 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -18,11 +18,11 @@ use std::process; use std::sync::Arc; use crate::config::persistence::pathfinder::GatewayPathfinder; +#[cfg(feature = "coconut")] +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; #[cfg(not(feature = "coconut"))] use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge; #[cfg(feature = "coconut")] -use coconut_interface::VerificationKey; -#[cfg(feature = "coconut")] use credentials::obtain_aggregate_verification_key; use self::storage::PersistentStorage; @@ -175,7 +175,7 @@ where &self, forwarding_channel: MixForwardingSender, active_clients_store: ActiveClientsStore, - #[cfg(feature = "coconut")] verification_key: VerificationKey, + #[cfg(feature = "coconut")] coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge, ) { info!("Starting client [web]socket listener..."); @@ -190,7 +190,7 @@ where Arc::clone(&self.identity_keypair), self.config.get_disabled_credentials_mode(), #[cfg(feature = "coconut")] - verification_key, + coconut_verifier, #[cfg(not(feature = "coconut"))] erc20_bridge, ) @@ -227,13 +227,27 @@ where ); } - // TODO: ask DH whether this function still makes sense in ^0.10 - async fn check_if_same_ip_gateway_exists(&self) -> Option { + fn random_api_client(&self) -> validator_client::ApiClient { let endpoints = self.config.get_validator_api_endpoints(); let validator_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - let validator_client = validator_client::ApiClient::new(validator_api.clone()); + + validator_client::ApiClient::new(validator_api.clone()) + } + + #[cfg(feature = "coconut")] + fn all_api_clients(&self) -> Vec { + self.config + .get_validator_api_endpoints() + .into_iter() + .map(validator_client::ApiClient::new) + .collect() + } + + // TODO: ask DH whether this function still makes sense in ^0.10 + async fn check_if_same_ip_gateway_exists(&self) -> Option { + let validator_client = self.random_api_client(); let existing_gateways = match validator_client.get_cached_gateways().await { Ok(gateways) => gateways, @@ -271,6 +285,9 @@ where obtain_aggregate_verification_key(&self.config.get_validator_api_endpoints()) .await .expect("failed to contact validators to obtain their verification keys"); + #[cfg(feature = "coconut")] + let coconut_verifier = + CoconutVerifier::new(self.all_api_clients(), validators_verification_key); #[cfg(not(feature = "coconut"))] let erc20_bridge = ERC20Bridge::new( @@ -291,7 +308,7 @@ where mix_forwarding_channel, active_clients_store, #[cfg(feature = "coconut")] - validators_verification_key, + Arc::new(coconut_verifier), #[cfg(not(feature = "coconut"))] erc20_bridge, ); diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 5e88cc8d69..27851e34bf 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -244,14 +244,18 @@ impl MixNode { atomic_verloc_results } - // TODO: ask DH whether this function still makes sense in ^0.10 - async fn check_if_same_ip_node_exists(&mut self) -> Option { + fn random_api_client(&self) -> validator_client::ApiClient { let endpoints = self.config.get_validator_api_endpoints(); let validator_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - let validator_client = validator_client::ApiClient::new(validator_api.clone()); + validator_client::ApiClient::new(validator_api.clone()) + } + + // TODO: ask DH whether this function still makes sense in ^0.10 + async fn check_if_same_ip_node_exists(&mut self) -> Option { + let validator_client = self.random_api_client(); let existing_nodes = match validator_client.get_cached_mixnodes().await { Ok(nodes) => nodes, Err(err) => { diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 0429c1a093..72377c7237 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -709,6 +709,15 @@ dependencies = [ "objc", ] +[[package]] +name = "coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "coconut-interface" version = "0.1.0" @@ -1096,6 +1105,42 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw3" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw4" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + [[package]] name = "darling" version = "0.10.2" @@ -2800,6 +2845,18 @@ dependencies = [ "time 0.3.7", ] +[[package]] +name = "multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", +] + [[package]] name = "native-tls" version = "0.2.8" @@ -5478,16 +5535,19 @@ dependencies = [ "async-trait", "base64", "bip39", + "coconut-bandwidth-contract-common", "coconut-interface", "colored", "config", "cosmrs", "cosmwasm-std", + "cw3", "flate2", "futures", "itertools", "log", "mixnet-contract-common", + "multisig-contract-common", "network-defaults", "prost", "reqwest", diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index cf8cde54fe..3184cab8c9 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -211,34 +211,31 @@ impl Config { .flat_map(|c| c.validators().cloned()) } - pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option { + pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks .mixnet_contract_address(network.into()) .expect("No mixnet contract address found in config") .parse() - .ok() + .expect("Wrong format for mixnet contract address") } - pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> Option { + pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks .vesting_contract_address(network.into()) .expect("No vesting contract address found in config") .parse() - .ok() + .expect("Wrong format for vesting contract address") } - pub fn get_bandwidth_claim_contract_address( - &self, - network: WalletNetwork, - ) -> Option { + pub fn get_bandwidth_claim_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks .bandwidth_claim_contract_address(network.into()) .expect("No bandwidth claim contract address found in config") .parse() - .ok() + .expect("Wrong format for bandwidth claim contract address") } pub fn select_validator_nymd_url(&mut self, nymd_url: Url, network: WalletNetwork) { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index f48cffe515..5e0215cf71 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -133,7 +133,7 @@ pub async fn switch_network( let denom = network.denom(); Account::new( - client.nymd.mixnet_contract_address()?.to_string(), + client.nymd.mixnet_contract_address().to_string(), client.nymd.address().to_string(), denom.parse()?, ) @@ -216,7 +216,7 @@ async fn _connect_with_mnemonic( .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.mixnet_contract_address().to_string(), client.nymd.address().to_string(), default_network.denom().parse()?, )), @@ -309,14 +309,12 @@ fn create_clients( 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), - ), + validator_client::Config::new(network.into(), nymd_url, api_url) + .with_mixnode_contract_address(config.get_mixnet_contract_address(network)) + .with_vesting_contract_address(config.get_vesting_contract_address(network)) + .with_bandwidth_claim_contract_address( + config.get_bandwidth_claim_contract_address(network), + ), mnemonic.clone(), )?; client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER); diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index cbfa8e39f9..01aac84999 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -18,7 +18,7 @@ pub async fn simulate_update_contract_settings( let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 37d48859aa..a833f934b1 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -20,7 +20,7 @@ pub async fn simulate_bond_gateway( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + 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 @@ -44,7 +44,7 @@ pub async fn simulate_unbond_gateway( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -68,7 +68,7 @@ pub async fn simulate_bond_mixnode( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -91,7 +91,7 @@ pub async fn simulate_unbond_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -112,7 +112,7 @@ pub async fn simulate_update_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -137,7 +137,7 @@ pub async fn simulate_delegate_to_mixnode( let delegation = amount.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -160,7 +160,7 @@ pub async fn simulate_undelegate_from_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index c72b7be820..fe095c5717 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -21,7 +21,7 @@ pub async fn simulate_vesting_bond_gateway( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -45,7 +45,7 @@ pub async fn simulate_vesting_unbond_gateway( let guard = state.read().await; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -69,7 +69,7 @@ pub async fn simulate_vesting_bond_mixnode( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -93,7 +93,7 @@ pub async fn simulate_vesting_unbond_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -114,7 +114,7 @@ pub async fn simulate_vesting_update_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -138,7 +138,7 @@ pub async fn simulate_withdraw_vested_coins( let amount = amount.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs index a212afe7f1..e6bb223787 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -14,7 +14,7 @@ pub async fn get_pending_vesting_delegation_events( ) -> Result, BackendError> { let guard = state.read().await; let client = &guard.current_client()?.nymd; - let vesting_contract = client.vesting_contract_address()?; + let vesting_contract = client.vesting_contract_address(); Ok(client .get_pending_delegation_events( diff --git a/nym-wallet/src/types/rust/gateway.ts b/nym-wallet/src/types/rust/gateway.ts index 1dbba0e5ed..24b3b7eb58 100644 --- a/nym-wallet/src/types/rust/gateway.ts +++ b/nym-wallet/src/types/rust/gateway.ts @@ -1,9 +1,2 @@ -export interface Gateway { - host: string; - mix_port: number; - clients_port: number; - location: string; - sphinx_key: string; - identity_key: string; - version: string; -} + +export interface Gateway { host: string, mix_port: number, clients_port: number, location: string, sphinx_key: string, identity_key: string, version: string, } \ No newline at end of file diff --git a/nym-wallet/src/types/rust/mixnode.ts b/nym-wallet/src/types/rust/mixnode.ts index d1c3a9574b..6b6d783dda 100644 --- a/nym-wallet/src/types/rust/mixnode.ts +++ b/nym-wallet/src/types/rust/mixnode.ts @@ -1,10 +1,2 @@ -export interface MixNode { - host: string; - mix_port: number; - verloc_port: number; - http_api_port: number; - sphinx_key: string; - identity_key: string; - version: string; - profit_margin_percent: number; -} + +export interface MixNode { host: string, mix_port: number, verloc_port: number, http_api_port: number, sphinx_key: string, identity_key: string, version: string, profit_margin_percent: number, } \ No newline at end of file diff --git a/nym-wallet/src/types/rust/rewardedsetnodestatus.ts b/nym-wallet/src/types/rust/rewardedsetnodestatus.ts index 8ddd13d047..3f0bb8e3e6 100644 --- a/nym-wallet/src/types/rust/rewardedsetnodestatus.ts +++ b/nym-wallet/src/types/rust/rewardedsetnodestatus.ts @@ -1 +1,2 @@ -export type RewardedSetNodeStatus = 'Active' | 'Standby'; + +export type RewardedSetNodeStatus = "Active" | "Standby"; \ No newline at end of file diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index ed31e57d8d..c33e43b55d 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -54,6 +54,7 @@ cosmwasm-std = "1.0.0-beta8" crypto = { path="../common/crypto" } gateway-client = { path="../common/client-libs/gateway-client" } mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" } +multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } nymsphinx = { path="../common/nymsphinx" } topology = { path="../common/topology" } validator-api-requests = { path = "validator-api-requests" } diff --git a/validator-api/src/coconut/client.rs b/validator-api/src/coconut/client.rs index 34042e9610..c606152cc9 100644 --- a/validator-api/src/coconut/client.rs +++ b/validator-api/src/coconut/client.rs @@ -2,9 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::error::Result; -use validator_client::nymd::TxResponse; +use multisig_contract_common::msg::ProposalResponse; +use validator_client::nymd::{Fee, TxResponse}; #[async_trait] pub trait Client { async fn get_tx(&self, tx_hash: &str) -> Result; + async fn get_proposal(&self, proposal_id: u64) -> Result; + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result; + async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option) + -> Result<()>; + async fn execute_proposal(&self, proposal_id: u64, fee: Option) -> Result<()>; } diff --git a/validator-api/src/coconut/error.rs b/validator-api/src/coconut/error.rs index fb2ff73419..78f9b08e43 100644 --- a/validator-api/src/coconut/error.rs +++ b/validator-api/src/coconut/error.rs @@ -63,8 +63,22 @@ pub enum CoconutError { #[error("Error in coconut interface - {0}")] CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + #[error("Could not create proposal for spending credential")] + CreateProposalError, + #[error("Storage error - {0}")] StorageError(#[from] ValidatorApiStorageError), + + #[error("Credentials error - {0}")] + CredentialsError(#[from] credentials::error::Error), + + #[error( + "Incorrect credential proposal description. Expected blinded serial number in base 58" + )] + IncorrectProposal, + + #[error("Internal error: {0}")] + InternalError(String), } impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError { diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index ed08605100..8d98cf977f 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -14,15 +14,19 @@ use crate::ValidatorApiStorage; use coconut_interface::{ Attribute, BlindSignRequest, BlindSignRequestBody, BlindedSignature, BlindedSignatureResponse, - KeyPair, Parameters, VerificationKeyResponse, + ExecuteReleaseFundsRequestBody, KeyPair, Parameters, ProposeReleaseFundsRequestBody, + ProposeReleaseFundsResponse, VerificationKey, VerificationKeyResponse, VerifyCredentialBody, + VerifyCredentialResponse, }; use config::defaults::VALIDATOR_API_VERSION; use credentials::coconut::params::{ ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, }; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::encryption; use crypto::shared_key::new_ephemeral_shared_key; use crypto::symmetric::stream_cipher; +use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; use getset::{CopyGetters, Getters}; use rand_07::rngs::OsRng; @@ -30,26 +34,33 @@ use rocket::fairing::AdHoc; use rocket::serde::json::Json; use rocket::State as RocketState; use std::sync::Arc; -use tokio::sync::{Mutex, RwLock}; -use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; +use tokio::sync::Mutex; +use url::Url; pub struct State { - client: Arc>, + client: Arc, key_pair: KeyPair, + validator_apis: Vec, storage: ValidatorApiStorage, rng: Arc>, } impl State { - pub(crate) fn new(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> Self + pub(crate) fn new( + client: C, + key_pair: KeyPair, + validator_apis: Vec, + storage: ValidatorApiStorage, + ) -> Self where C: LocalClient + Send + Sync + 'static, { - let client = Arc::new(RwLock::new(client)); + let client = Arc::new(client); let rng = Arc::new(Mutex::new(OsRng)); Self { client, key_pair, + validator_apis, storage, rng, } @@ -109,6 +120,10 @@ impl State { Ok(response) } } + + pub async fn verification_key(&self) -> Result { + Ok(obtain_aggregate_verification_key(&self.validator_apis).await?) + } } #[derive(Getters, CopyGetters, Debug)] @@ -135,11 +150,16 @@ impl InternalSignRequest { } } - pub fn stage(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> AdHoc + pub fn stage( + client: C, + key_pair: KeyPair, + validator_apis: Vec, + storage: ValidatorApiStorage, + ) -> AdHoc where C: LocalClient + Send + Sync + 'static, { - let state = State::new(client, key_pair, storage); + let state = State::new(client, key_pair, validator_apis, storage); AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(state).mount( // this format! is so ugly... @@ -150,7 +170,10 @@ impl InternalSignRequest { routes![ post_blind_sign, get_verification_key, - post_partial_bandwidth_credential + post_partial_bandwidth_credential, + verify_bandwidth_credential, + post_propose_release_funds, + post_execute_release_funds ], ) }) @@ -183,8 +206,6 @@ pub async fn post_blind_sign( } let tx = state .client - .read() - .await .get_tx(blind_sign_request_body.tx_hash()) .await?; let encryption_key = extract_encryption_key(&blind_sign_request_body, tx).await?; @@ -226,3 +247,69 @@ pub async fn get_verification_key( state.key_pair.verification_key(), ))) } + +#[post("/verify-bandwidth-credential", data = "")] +pub async fn verify_bandwidth_credential( + verify_credential_body: Json, + state: &RocketState, +) -> Result> { + let proposal_id = *verify_credential_body.0.proposal_id(); + let proposal = state.client.get_proposal(proposal_id).await?; + // Proposal description is the blinded serial number + if !verify_credential_body + .0 + .credential() + .has_blinded_serial_number(&proposal.description)? + { + return Err(CoconutError::IncorrectProposal); + } + let verification_key = state.verification_key().await?; + let verification_result = verify_credential_body + .0 + .credential() + .verify(&verification_key); + + // Vote yes or no on the proposal based on the verification result + state + .client + .vote_proposal(proposal_id, verification_result, None) + .await?; + + Ok(Json(VerifyCredentialResponse::new(verification_result))) +} + +#[post("/propose-release-funds", data = "")] +pub async fn post_propose_release_funds( + propose_release_funds: Json, + state: &RocketState, +) -> Result> { + let verification_key = state.verification_key().await?; + if !propose_release_funds + .0 + .credential() + .verify(&verification_key) + { + return Err(CoconutError::CreateProposalError); + } + + let title = String::from("Create proposal to spend a coconut credential"); + let blinded_serial_number = propose_release_funds.0.credential().blinded_serial_number(); + let voucher_value = propose_release_funds.0.credential().voucher_value() as u128; + let proposal_id = state + .client + .propose_release_funds(title, blinded_serial_number, voucher_value, None) + .await?; + + Ok(Json(ProposeReleaseFundsResponse::new(proposal_id))) +} + +#[post("/execute-release-funds", data = "")] +pub async fn post_execute_release_funds( + execute_release_funds: Json, + state: &RocketState, +) -> Result> { + let proposal_id = *execute_release_funds.0.proposal_id(); + state.client.execute_proposal(proposal_id, None).await?; + + Ok(Json(())) +} diff --git a/validator-api/src/coconut/tests.rs b/validator-api/src/coconut/tests.rs index 5f0c745a69..5e34f3962c 100644 --- a/validator-api/src/coconut/tests.rs +++ b/validator-api/src/coconut/tests.rs @@ -15,10 +15,11 @@ use credentials::coconut::params::{ }; use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; +use multisig_contract_common::msg::ProposalResponse; use nymcoconut::{ prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, }; -use validator_client::nymd::{tx::Hash, DeliverTx, Event, Tag, TxResponse}; +use validator_client::nymd::{tx::Hash, DeliverTx, Event, Fee, Tag, TxResponse}; use validator_client::validator_api::routes::{ API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, COCONUT_ROUTES, COCONUT_VERIFICATION_KEY, @@ -56,6 +57,37 @@ impl super::client::Client for DummyClient { .cloned() .ok_or(CoconutError::TxHashParseError) } + + async fn get_proposal(&self, _proposal_id: u64) -> Result { + todo!() + } + + async fn propose_release_funds( + &self, + _title: String, + _blinded_serial_number: String, + _voucher_value: u128, + _fee: Option, + ) -> Result { + todo!() + } + + async fn vote_proposal( + &self, + _proposal_id: u64, + _vote_yes: bool, + _fee: Option, + ) -> Result<()> { + todo!() + } + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> crate::coconut::error::Result<()> { + todo!() + } } pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse { @@ -87,7 +119,12 @@ async fn check_signer_verif_key(key_pair: KeyPair) { let nymd_db = Arc::new(RwLock::new(HashMap::new())); let nymd_client = DummyClient::new(&nymd_db); - let rocket = rocket::build().attach(InternalSignRequest::stage(nymd_client, key_pair, storage)); + let rocket = rocket::build().attach(InternalSignRequest::stage( + nymd_client, + key_pair, + vec![], + storage, + )); let client = Client::tracked(rocket) .await @@ -172,6 +209,7 @@ async fn signed_before() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, + vec![], storage.clone(), )); let client = Client::tracked(rocket) @@ -231,7 +269,7 @@ async fn state_functions() { let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let state = State::new(nymd_client, key_pair, storage.clone()); + let state = State::new(nymd_client, key_pair, vec![], storage.clone()); let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E"); assert!(state.signed_before(&tx_hash).await.unwrap().is_none()); @@ -393,6 +431,7 @@ async fn blind_sign_correct() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, + vec![], storage.clone(), )); let client = Client::tracked(rocket) @@ -465,6 +504,7 @@ async fn signature_test() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, + vec![], storage.clone(), )); let client = Client::tracked(rocket) diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index c3a07d145a..7cf81b296e 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -2,16 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::defaults::{default_api_endpoints, DEFAULT_NETWORK}; +use config::defaults::DEFAULT_NETWORK; use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::time::Duration; use url::Url; -#[cfg(feature = "coconut")] -use coconut_interface::{Base58, KeyPair}; - mod template; pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; @@ -82,17 +79,20 @@ impl NymConfig for Config { } fn config_directory(&self) -> PathBuf { - self.root_directory().join("config") + self.root_directory().join(self.get_id()).join("config") } fn data_directory(&self) -> PathBuf { - self.root_directory().join("data") + self.root_directory().join(self.get_id()).join("data") } } #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] pub struct Base { + /// ID specifies the human readable ID of this particular validator-api. + id: String, + local_validator: Url, /// Address of the validator contract managing the network @@ -105,6 +105,7 @@ pub struct Base { impl Default for Base { fn default() -> Self { Base { + id: String::default(), local_validator: DEFAULT_LOCAL_VALIDATOR .parse() .expect("default local validator is malformed!"), @@ -128,11 +129,6 @@ pub struct NetworkMonitor { #[serde(default)] disabled_credentials_mode: bool, - /// Specifies list of all validators on the network issuing coconut credentials. - /// A special care must be taken to ensure they are in correct order. - /// The list must also contain THIS validator that is running the test - all_validator_apis: Vec, - /// Specifies the interval at which the network monitor sends the test packets. #[serde(with = "humantime_serde")] run_interval: Duration, @@ -189,8 +185,10 @@ pub struct NetworkMonitor { } impl NetworkMonitor { + pub const DB_FILE: &'static str = "credentials_database.db"; + fn default_credentials_database_path() -> PathBuf { - Config::default_data_directory(None).join("credentials_database.db") + Config::default_data_directory(None).join(Self::DB_FILE) } } @@ -201,7 +199,6 @@ impl Default for NetworkMonitor { min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY, enabled: false, disabled_credentials_mode: true, - all_validator_apis: default_api_endpoints(), run_interval: DEFAULT_MONITOR_RUN_INTERVAL, gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL, gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE, @@ -230,8 +227,10 @@ pub struct NodeStatusAPI { } impl NodeStatusAPI { + pub const DB_FILE: &'static str = "db.sqlite"; + fn default_database_path() -> PathBuf { - Config::default_data_directory(None).join("db.sqlite") + Config::default_data_directory(None).join(Self::DB_FILE) } } @@ -279,15 +278,31 @@ impl Default for Rewarding { } } -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] #[cfg(feature = "coconut")] pub struct CoconutSigner { /// Specifies whether rewarding service is enabled in this process. enabled: bool, - /// Base58 encoded signing keypair - keypair_bs58: String, + /// Path to the signing keypair + keypair_path: PathBuf, + + /// Specifies list of all validators on the network issuing coconut credentials. + /// A special care must be taken to ensure they are in correct order. + /// The list must also contain THIS validator that is running the test + all_validator_apis: Vec, +} + +#[cfg(feature = "coconut")] +impl Default for CoconutSigner { + fn default() -> Self { + CoconutSigner { + enabled: false, + keypair_path: PathBuf::default(), + all_validator_apis: config::defaults::default_api_endpoints(), + } + } } impl Config { @@ -295,9 +310,18 @@ impl Config { Config::default() } + pub fn with_id(mut self, id: &str) -> Self { + self.base.id = id.to_string(); + self.node_status_api.database_path = + Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE); + self.network_monitor.credentials_database_path = + Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE); + self + } + #[cfg(feature = "coconut")] - pub fn keypair(&self) -> KeyPair { - KeyPair::try_from_bs58(self.coconut_signer.keypair_bs58.clone()).unwrap() + pub fn keypair_path(&self) -> PathBuf { + self.coconut_signer.keypair_path.clone() } pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self { @@ -337,13 +361,14 @@ impl Config { } #[cfg(feature = "coconut")] - pub fn with_keypair>(mut self, keypair_bs58: S) -> Self { - self.coconut_signer.keypair_bs58 = keypair_bs58.into(); + pub fn with_keypair_path(mut self, keypair_path: PathBuf) -> Self { + self.coconut_signer.keypair_path = keypair_path; self } + #[cfg(feature = "coconut")] pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { - self.network_monitor.all_validator_apis = validator_api_urls; + self.coconut_signer.all_validator_apis = validator_api_urls; self } @@ -374,6 +399,10 @@ impl Config { self } + pub fn get_id(&self) -> String { + self.base.id.clone() + } + pub fn get_network_monitor_enabled(&self) -> bool { self.network_monitor.enabled } @@ -474,7 +503,7 @@ impl Config { // fix dead code warnings as this method is only ever used with coconut feature #[cfg(feature = "coconut")] pub fn get_all_validator_api_endpoints(&self) -> Vec { - self.network_monitor.all_validator_apis.clone() + self.coconut_signer.all_validator_apis.clone() } // TODO: Remove if still unused diff --git a/validator-api/src/config/template.rs b/validator-api/src/config/template.rs index e5edd71c4e..74eb8ca10b 100644 --- a/validator-api/src/config/template.rs +++ b/validator-api/src/config/template.rs @@ -10,12 +10,18 @@ pub(crate) fn config_template() -> &'static str { [base] +# ID specifies the human readable ID of this particular validator-api. +id = '{{ base.id }}' + # Validator server to which the API will be getting information about the network. local_validator = '{{ base.local_validator }}' # Address of the validator contract managing the network. mixnet_contract_address = '{{ base.mixnet_contract_address }}' +# Mnemonic used for rewarding and validator interaction +mnemonic = '{{ base.mnemonic }}' + ##### network monitor config options ##### [network_monitor] @@ -26,15 +32,6 @@ enabled = {{ network_monitor.enabled }} # to claim bandwidth without presenting bandwidth credentials. disabled_credentials_mode = {{ network_monitor.disabled_credentials_mode }} -# Specifies list of all validators on the network issuing coconut credentials. -# A special care must be taken to ensure they are in correct order. -# The list must also contain THIS validator that is running the test -all_validator_apis = [ - {{#each network_monitor.all_validator_apis }} - '{{this}}', - {{/each}} -] - # Specifies the interval at which the network monitor sends the test packets. run_interval = '{{ network_monitor.run_interval }}' @@ -92,13 +89,27 @@ database_path = '{{ node_status_api.database_path }}' # Specifies whether rewarding service is enabled in this process. enabled = {{ rewarding.enabled }} -# Mnemonic (currently of the network monitor) used for rewarding -mnemonic = '{{ rewarding.mnemonic }}' - # Specifies the minimum percentage of monitor test run data present in order to # distribute rewards for given interval. # Note, only values in range 0-100 are valid minimum_interval_monitor_threshold = {{ rewarding.minimum_interval_monitor_threshold }} +[coconut_signer] + +# Specifies whether rewarding service is enabled in this process. +enabled = {{ coconut_signer.enabled }} + +# Path to the signing keypair +keypair_path = '{{ coconut_signer.keypair_path }}' + +# Specifies list of all validators on the network issuing coconut credentials. +# A special care must be taken to ensure they are in correct order. +# The list must also contain THIS validator that is running the test +all_validator_apis = [ + {{#each coconut_signer.all_validator_apis }} + '{{this}}', + {{/each}} +] + "# } diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 26cd59bd1c..e51a9fd847 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -23,17 +23,18 @@ use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; use rocket_okapi::mount_endpoints_and_merged_docs; use rocket_okapi::swagger_ui::make_swagger_ui; -use std::process; use std::sync::Arc; use std::time::Duration; +use std::{fs, process}; use tokio::sync::Notify; -use url::Url; // use validator_client::nymd::SigningNymdClient; // use validator_client::ValidatorClientError; use crate::rewarded_set_updater::RewardedSetUpdater; #[cfg(feature = "coconut")] use coconut::InternalSignRequest; +#[cfg(feature = "coconut")] +use coconut_interface::{Base58, KeyPair}; use validator_client::nymd::SigningNymdClient; pub(crate) mod config; @@ -48,18 +49,19 @@ mod swagger; #[cfg(feature = "coconut")] mod coconut; +const ID: &str = "id"; const MONITORING_ENABLED: &str = "enable-monitor"; const REWARDING_ENABLED: &str = "enable-rewarding"; const MIXNET_CONTRACT_ARG: &str = "mixnet-contract"; const MNEMONIC_ARG: &str = "mnemonic"; const WRITE_CONFIG_ARG: &str = "save-config"; const NYMD_VALIDATOR_ARG: &str = "nymd-validator"; -const API_VALIDATORS_ARG: &str = "api-validators"; const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode"; +#[cfg(feature = "coconut")] +const API_VALIDATORS_ARG: &str = "api-validators"; #[cfg(feature = "coconut")] const KEYPAIR_ARG: &str = "keypair"; - #[cfg(feature = "coconut")] const COCONUT_ENABLED: &str = "enable-coconut"; @@ -73,7 +75,8 @@ const REWARDING_MONITOR_THRESHOLD_ARG: &str = "monitor-threshold"; const MIN_MIXNODE_RELIABILITY_ARG: &str = "min_mixnode_reliability"; const MIN_GATEWAY_RELIABILITY_ARG: &str = "min_gateway_reliability"; -fn parse_validators(raw: &str) -> Vec { +#[cfg(feature = "coconut")] +fn parse_validators(raw: &str) -> Vec { raw.split(',') .map(|raw_validator| { raw_validator @@ -124,6 +127,12 @@ fn parse_args<'a>() -> ArgMatches<'a> { .version(crate_version!()) .long_version(&*build_details) .author("Nymtech") + .arg( + Arg::with_name(ID) + .help("Id of the validator-api we want to run") + .long(ID) + .takes_value(true) + ) .arg( Arg::with_name(MONITORING_ENABLED) .help("specifies whether a network monitoring is enabled on this API") @@ -159,12 +168,6 @@ fn parse_args<'a>() -> ArgMatches<'a> { .long(WRITE_CONFIG_ARG) .short("w") ) - .arg( - Arg::with_name(API_VALIDATORS_ARG) - .help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered") - .long(API_VALIDATORS_ARG) - .takes_value(true) - ) .arg( Arg::with_name(REWARDING_MONITOR_THRESHOLD_ARG) .help("Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval.") @@ -185,10 +188,16 @@ fn parse_args<'a>() -> ArgMatches<'a> { .takes_value(true) .long(KEYPAIR_ARG), ) + .arg( + Arg::with_name(API_VALIDATORS_ARG) + .help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered") + .long(API_VALIDATORS_ARG) + .takes_value(true) + ) .arg( Arg::with_name(COCONUT_ENABLED) .help("Flag to indicate whether coconut signer authority is enabled on this API") - .requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG]) + .requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG, API_VALIDATORS_ARG]) .long(COCONUT_ENABLED), ); @@ -236,12 +245,18 @@ fn setup_logging() { .filter_module("sled", log::LevelFilter::Warn) .filter_module("tungstenite", log::LevelFilter::Warn) .filter_module("tokio_tungstenite", log::LevelFilter::Warn) - .filter_module("_", log::LevelFilter::Warn) - .filter_module("rocket::server", log::LevelFilter::Warn) .init(); } fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { + if let Some(id) = matches.value_of(ID) { + fs::create_dir_all(Config::default_config_directory(Some(id))) + .expect("Could not create config directory"); + fs::create_dir_all(Config::default_data_directory(Some(id))) + .expect("Could not create data directory"); + config = config.with_id(id); + } + if matches.is_present(MONITORING_ENABLED) { config = config.with_network_monitor_enabled(true) } @@ -255,6 +270,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { config = config.with_coconut_signer_enabled(true) } + #[cfg(feature = "coconut")] if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) { config = config.with_custom_validator_apis(parse_validators(raw_validators)); } @@ -311,11 +327,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { #[cfg(feature = "coconut")] if let Some(keypair_path) = matches.value_of(KEYPAIR_ARG) { - let keypair_bs58 = std::fs::read_to_string(keypair_path) - .unwrap() - .trim() - .to_string(); - config = config.with_keypair(keypair_bs58) + config = config.with_keypair_path(keypair_path.into()) } #[cfg(not(feature = "coconut"))] @@ -402,7 +414,7 @@ fn expected_monitor_test_runs(config: &Config, interval_length: Duration) -> usi async fn setup_rocket( config: &Config, liftoff_notify: Arc, - _nymd_client: Option>, + _nymd_client: Client, ) -> Result> { let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); let mut rocket = rocket::build(); @@ -434,9 +446,14 @@ async fn setup_rocket( #[cfg(feature = "coconut")] let rocket = if config.get_coconut_signer_enabled() { + let keypair_bs58 = fs::read_to_string(config.keypair_path())? + .trim() + .to_string(); + let keypair = KeyPair::try_from_bs58(keypair_bs58)?; rocket.attach(InternalSignRequest::stage( - _nymd_client.expect("Should have a signing client here"), - config.keypair(), + _nymd_client, + keypair, + config.get_all_validator_api_endpoints(), storage.clone().unwrap(), )) } else { @@ -486,10 +503,11 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { let system_version = env!("CARGO_PKG_VERSION"); // try to load config from the file, if it doesn't exist, use default values - let config = match Config::load_from_file(None) { + let id = matches.value_of(ID); + let config = match Config::load_from_file(id) { Ok(cfg) => cfg, Err(_) => { - let config_path = Config::default_config_file_path(None) + let config_path = Config::default_config_file_path(id) .into_os_string() .into_string() .unwrap(); @@ -507,11 +525,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { return Ok(()); } - let signing_nymd_client = if matches.is_present(MNEMONIC_ARG) { - Some(Client::new_signing(&config)) - } else { - None - }; + let signing_nymd_client = Client::new_signing(&config); let liftoff_notify = Arc::new(Notify::new()); @@ -529,9 +543,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { // if network monitor is disabled, we're not going to be sending any rewarding hence // we're not starting signing client if config.get_network_monitor_enabled() { - let nymd_client = signing_nymd_client.expect("We should have a signing client here"); let validator_cache_refresher = ValidatorCacheRefresher::new( - nymd_client.clone(), + signing_nymd_client.clone(), config.get_caching_interval(), validator_cache.clone(), ); @@ -546,7 +559,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { tokio::spawn(async move { uptime_updater.run().await }); let mut rewarded_set_updater = - RewardedSetUpdater::new(nymd_client, validator_cache.clone(), storage).await?; + RewardedSetUpdater::new(signing_nymd_client, validator_cache.clone(), storage).await?; // spawn rewarded set updater tokio::spawn(async move { rewarded_set_updater.run().await.unwrap() }); diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 3e32d40bef..b2b43b179d 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -1,21 +1,28 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; -use crate::rewarded_set_updater::error::RewardingError; #[cfg(feature = "coconut")] use async_trait::async_trait; -use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT}; -use mixnet_contract_common::Interval; -use mixnet_contract_common::{ - reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond, - IdentityKey, MixNodeBond, MixnodeRewardingStatusResponse, RewardedSetNodeStatus, -}; use serde::Serialize; +#[cfg(feature = "coconut")] +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; use tokio::time::sleep; + +use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT}; +use mixnet_contract_common::{ + reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond, + IdentityKey, Interval, MixNodeBond, MixnodeRewardingStatusResponse, RewardedSetNodeStatus, +}; +#[cfg(feature = "coconut")] +use multisig_contract_common::msg::ProposalResponse; +#[cfg(feature = "coconut")] +use validator_client::nymd::{ + cosmwasm_client::logs::find_attribute, + traits::{MultisigSigningClient, QueryClient}, +}; use validator_client::nymd::{ hash::{Hash, SHA256_HASH_SIZE}, Coin, CosmWasmClient, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, @@ -23,6 +30,11 @@ use validator_client::nymd::{ }; use validator_client::ValidatorClientError; +#[cfg(feature = "coconut")] +use crate::coconut::error::CoconutError; +use crate::config::Config; +use crate::rewarded_set_updater::error::RewardingError; + pub(crate) struct Client(pub(crate) Arc>>); impl Clone for Client { @@ -46,14 +58,8 @@ impl Client { .parse() .expect("the mixnet contract address is invalid!"); - let client_config = validator_client::Config::new( - network, - nymd_url, - api_url, - Some(mixnet_contract), - None, - None, - ); + let client_config = validator_client::Config::new(network, nymd_url, api_url) + .with_mixnode_contract_address(mixnet_contract); let inner = validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"); @@ -80,14 +86,8 @@ impl Client { .parse() .expect("the mnemonic is invalid!"); - let client_config = validator_client::Config::new( - network, - nymd_url, - api_url, - Some(mixnet_contract), - None, - None, - ); + let client_config = validator_client::Config::new(network, nymd_url, api_url) + .with_mixnode_contract_address(mixnet_contract); let inner = validator_client::Client::new_signing(client_config, mnemonic) .expect("Failed to connect to nymd!"); @@ -366,12 +366,7 @@ impl Client { C: SigningCosmWasmClient + Sync, M: Serialize + Clone + Send, { - let contract = self - .0 - .read() - .await - .get_mixnet_contract_address() - .ok_or(RewardingError::UnspecifiedContractAddress)?; + let contract = self.0.read().await.get_mixnet_contract_address(); // grab the write lock here so we're sure nothing else is executing anything on the contract // in the meantime @@ -420,7 +415,7 @@ impl Client { #[cfg(feature = "coconut")] impl crate::coconut::client::Client for Client where - C: CosmWasmClient + Sync + Send, + C: SigningCosmWasmClient + Sync + Send, { async fn get_tx( &self, @@ -428,7 +423,71 @@ where ) -> crate::coconut::error::Result { let tx_hash = tx_hash .parse::() - .map_err(|_| crate::coconut::error::CoconutError::TxHashParseError)?; + .map_err(|_| CoconutError::TxHashParseError)?; Ok(self.0.read().await.nymd.get_tx(tx_hash).await?) } + + async fn get_proposal( + &self, + proposal_id: u64, + ) -> crate::coconut::error::Result { + Ok(self.0.read().await.nymd.get_proposal(proposal_id).await?) + } + + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result { + let res = self + .0 + .read() + .await + .nymd + .propose_release_funds(title, blinded_serial_number, voucher_value, fee) + .await?; + let proposal_id = u64::from_str( + &find_attribute(&res.logs, "wasm", "proposal_id") + .ok_or_else(|| { + CoconutError::InternalError("No attribute with proposal_id as key".to_string()) + })? + .value, + ) + .map_err(|_| { + CoconutError::InternalError("proposal_id could not be parsed to u64".to_string()) + })?; + + Ok(proposal_id) + } + + async fn vote_proposal( + &self, + proposal_id: u64, + vote_yes: bool, + fee: Option, + ) -> Result<(), CoconutError> { + self.0 + .read() + .await + .nymd + .vote_proposal(proposal_id, vote_yes, fee) + .await?; + Ok(()) + } + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> Result<(), CoconutError> { + self.0 + .read() + .await + .nymd + .execute_proposal(proposal_id, fee) + .await?; + Ok(()) + } } diff --git a/validator-api/src/rewarded_set_updater/error.rs b/validator-api/src/rewarded_set_updater/error.rs index 3c866a9c98..20f0edc277 100644 --- a/validator-api/src/rewarded_set_updater/error.rs +++ b/validator-api/src/rewarded_set_updater/error.rs @@ -8,9 +8,6 @@ use validator_client::ValidatorClientError; #[derive(Debug, Error)] pub enum RewardingError { - #[error("Could not distribute rewards as the contract address was unspecified")] - UnspecifiedContractAddress, - // #[error("There were no mixnodes to reward (network is dead)")] // NoMixnodesToReward, #[error("Failed to execute the smart contract - {0}")] From ec799cf17c26b71ff62e90dd9831cca3f1ecab4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Walther?= Date: Thu, 2 Jun 2022 18:59:23 +0200 Subject: [PATCH 20/24] Add sccache to wallet workflow --- .github/workflows/wallet.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/wallet.yml b/.github/workflows/wallet.yml index 8c2570ed13..44e593d9cb 100644 --- a/.github/workflows/wallet.yml +++ b/.github/workflows/wallet.yml @@ -11,6 +11,8 @@ on: jobs: build: runs-on: [ self-hosted, custom-linux ] + env: + RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools From 1bdaab6c9713eba67ce712ca3efccc4152253372 Mon Sep 17 00:00:00 2001 From: durch Date: Mon, 6 Jun 2022 14:17:59 +0200 Subject: [PATCH 21/24] Fix underflow in rewards estimation --- common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 4325727195..b8622e0f11 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -458,7 +458,7 @@ impl MixNodeBond { .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 = node_profit - operator_reward; + let delegators_reward = node_profit.saturating_sub(operator_reward); Ok(RewardEstimate { total_node_reward: total_node_reward.try_into()?, From 459db5718ec2cccf6d60de6a9cbbc78975d7e3a9 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 6 Jun 2022 14:19:24 +0200 Subject: [PATCH 22/24] Set probability to 1 if less nodes then the limit (#1306) * Set probability to 1 if less nodes then the limit * Check both sets independantly --- validator-api/src/node_status_api/routes.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index 22ccbee404..f82206e32b 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -211,6 +211,7 @@ pub(crate) async fn get_mixnode_inclusion_probability( identity: String, ) -> Json> { let mixnodes = cache.mixnodes().await; + let rewarding_params = cache.epoch_reward_params().await.into_inner(); if let Some(target_mixnode) = mixnodes.iter().find(|x| x.identity() == &identity) { let total_bonded_tokens = mixnodes @@ -218,17 +219,23 @@ pub(crate) async fn get_mixnode_inclusion_probability( .fold(0u128, |acc, x| acc + x.total_bond().unwrap_or_default()) as f64; - let rewarding_params = cache.epoch_reward_params().await.into_inner(); let rewarded_set_size = rewarding_params.rewarded_set_size() as f64; let active_set_size = rewarding_params.active_set_size() as f64; let prob_one_draw = target_mixnode.total_bond().unwrap_or_default() as f64 / total_bonded_tokens; // Chance to be selected in any draw for active set - let prob_active_set = active_set_size * prob_one_draw; + let prob_active_set = if mixnodes.len() <= active_set_size as usize { + 1.0 + } else { + active_set_size * prob_one_draw + }; // This is likely slightly too high, as we're not correcting form them not being selected in active, should be chance to be selected, minus the chance for being not selected in reserve - let prob_reserve_set = (rewarded_set_size - active_set_size) * prob_one_draw; - // (rewarded_set_size - active_set_size) * prob_one_draw * (1. - prob_active_set); + let prob_reserve_set = if mixnodes.len() <= rewarded_set_size as usize { + 1.0 + } else { + (rewarded_set_size - active_set_size) * prob_one_draw + }; Json(Some(InclusionProbabilityResponse { in_active: prob_active_set.into(), From 27a4a447175e3aa2f7885d857866f177e50b2b44 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 6 Jun 2022 14:19:36 +0200 Subject: [PATCH 23/24] Claim and compound reward wallet endpoints (#1302) * Add claim and compound wallet endpoints, proc_macro to generate execute and simulate * CHANGELOG * Sort CHANGELOG lines * PR comments --- CHANGELOG.md | 30 +++-- Cargo.lock | 9 ++ Cargo.toml | 1 + .../client-libs/validator-client/Cargo.toml | 1 + .../validator-client/src/nymd/mod.rs | 122 ++++++++++-------- .../src/nymd/traits/vesting_signing_client.rs | 96 -------------- common/execute/Cargo.toml | 11 ++ common/execute/src/lib.rs | 110 ++++++++++++++++ nym-wallet/Cargo.lock | 9 ++ nym-wallet/src-tauri/src/main.rs | 16 +++ .../src-tauri/src/operations/mixnet/mod.rs | 1 + .../src/operations/mixnet/rewards.rs | 53 ++++++++ .../src/operations/simulate/mixnet.rs | 48 +++++++ .../src/operations/simulate/vesting.rs | 48 +++++++ .../src-tauri/src/operations/vesting/mod.rs | 1 + .../src/operations/vesting/rewards.rs | 52 ++++++++ 16 files changed, 441 insertions(+), 167 deletions(-) create mode 100644 common/execute/Cargo.toml create mode 100644 common/execute/src/lib.rs create mode 100644 nym-wallet/src-tauri/src/operations/mixnet/rewards.rs create mode 100644 nym-wallet/src-tauri/src/operations/vesting/rewards.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 50dc0d6fdb..f9c3f58406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,40 +4,41 @@ ### Added +- all: added network compilation target to `--help` (or `--version`) commands ([#1256]). - explorer-api: learned how to sum the delegations by owner in a new endpoint. +- gateway: Added gateway coconut verifications and validator-api communication for double spending protection ([#1261]) +- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292]) +- mixnet-contract: Replace all naked `-` with `saturating_sub`. +- 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]). +- validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284]) - validator-api: add detailed mixnode bond endpoints, and explorer-api makes use of that data to append stake saturation. -- wallet: require password to switch accounts +- validator-api: add Swagger to document the REST API ([#1249]). +- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261]) +- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292]) - wallet: add simple CLI tool for decrypting and recovering the wallet file. - wallet: added support for multiple accounts ([#1265]) +- wallet: compound and claim reward endpoints for operators and delegators ([#1302]) +- wallet: require password to switch accounts - 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]). -- 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]). -- gateway: Added gateway coconut verifications and validator-api communication for double spending protection ([#1261]) -- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261]) ### 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: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284]) - 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: replaced integer division with fixed for performance calculations ([#1284]) - 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]) +- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275]) ### 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 [#1257]: https://github.com/nymtech/nym/pull/1257 +[#1258]: https://github.com/nymtech/nym/pull/1258 [#1260]: https://github.com/nymtech/nym/pull/1260 [#1261]: https://github.com/nymtech/nym/pull/1261 [#1265]: https://github.com/nymtech/nym/pull/1265 @@ -47,6 +48,7 @@ [#1284]: https://github.com/nymtech/nym/pull/1284 [#1292]: https://github.com/nymtech/nym/pull/1292 [#1295]: https://github.com/nymtech/nym/pull/1295 +[#1302]: https://github.com/nymtech/nym/pull/1302 ## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04) diff --git a/Cargo.lock b/Cargo.lock index 5df601a7b7..26e5c5b3f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1602,6 +1602,14 @@ dependencies = [ "uint", ] +[[package]] +name = "execute" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "explorer-api" version = "1.0.1" @@ -6257,6 +6265,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "cw3", + "execute", "flate2", "futures", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 131fd2dbb4..1f36d4b26e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "common/credentials", "common/crypto", "common/crypto/dkg", + "common/execute", "common/bandwidth-claim-contract", "common/cosmwasm-smart-contracts/coconut-bandwidth-contract", "common/cosmwasm-smart-contracts/contracts-common", diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 560dcb4788..6c9fa9faf5 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -41,6 +41,7 @@ flate2 = { version = "1.0.20", optional = true } sha2 = { version = "0.9.5", optional = true } itertools = { version = "0.10", optional = true } cosmwasm-std = { version = "1.0.0-beta8", optional = true } +execute = { path = "../../execute" } [dev-dependencies] ts-rs = "6.1.2" diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 1851ce155d..e680a57e6a 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -14,6 +14,7 @@ use cosmrs::rpc::Error as TendermintRpcError; use cosmrs::rpc::HttpClientUrl; use cosmrs::tx::Msg; use cosmwasm_std::Uint128; +use execute::execute; pub use fee::gas_price::GasPrice; use mixnet_contract_common::mixnode::DelegationEvent; use mixnet_contract_common::{ @@ -26,6 +27,7 @@ use mixnet_contract_common::{ use network_defaults::DEFAULT_NETWORK; use serde::Serialize; use std::convert::TryInto; +use vesting_contract_common::ExecuteMsg as VestingExecuteMsg; pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; @@ -848,87 +850,93 @@ impl NymdClient { .await } - pub async fn compound_operator_reward( - &self, - fee: Option, - ) -> Result + #[execute("mixnet")] + fn _compound_operator_reward(&self, fee: Option) -> (ExecuteMsg, Option) 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 + (ExecuteMsg::CompoundOperatorReward {}, fee) } - pub async fn claim_operator_reward(&self, fee: Option) -> Result + #[execute("mixnet")] + fn _claim_operator_reward(&self, fee: Option) -> (ExecuteMsg, Option) 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 + (ExecuteMsg::ClaimOperatorReward {}, fee) } - pub async fn compound_delegator_reward( + #[execute("mixnet")] + fn _compound_delegator_reward( &self, mix_identity: IdentityKey, fee: Option, - ) -> Result + ) -> (ExecuteMsg, Option) 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 + (ExecuteMsg::CompoundDelegatorReward { mix_identity }, fee) } - pub async fn claim_delegator_reward( + #[execute("mixnet")] + fn _claim_delegator_reward( &self, mix_identity: IdentityKey, fee: Option, - ) -> Result + ) -> (ExecuteMsg, Option) 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 + (ExecuteMsg::ClaimDelegatorReward { mix_identity }, fee) + } + + #[execute("vesting")] + fn _vesting_compound_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> (VestingExecuteMsg, Option) + where + C: SigningCosmWasmClient + Sync, + { + ( + VestingExecuteMsg::CompoundDelegatorReward { mix_identity }, + fee, + ) + } + + #[execute("vesting")] + fn _vesting_claim_operator_reward(&self, fee: Option) -> (VestingExecuteMsg, Option) + where + C: SigningCosmWasmClient + Sync, + { + (VestingExecuteMsg::ClaimOperatorReward {}, fee) + } + + #[execute("vesting")] + fn _vesting_compound_operator_reward( + &self, + fee: Option, + ) -> (VestingExecuteMsg, Option) + where + C: SigningCosmWasmClient + Sync, + { + (VestingExecuteMsg::CompoundOperatorReward {}, fee) + } + + #[execute("vesting")] + fn _vesting_claim_delegator_reward( + &self, + mix_identity: IdentityKey, + fee: Option, + ) -> (VestingExecuteMsg, Option) + where + C: SigningCosmWasmClient + Sync, + { + ( + VestingExecuteMsg::ClaimDelegatorReward { mix_identity }, + fee, + ) } /// Announce a mixnode, paying a fee. 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 e24473e81d..957a02e3e1 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 @@ -11,28 +11,6 @@ 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, @@ -397,78 +375,4 @@ 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/execute/Cargo.toml b/common/execute/Cargo.toml new file mode 100644 index 0000000000..551f1728fb --- /dev/null +++ b/common/execute/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "execute" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1", features = ["full"] } +quote = "1" diff --git a/common/execute/src/lib.rs b/common/execute/src/lib.rs new file mode 100644 index 0000000000..795a72822d --- /dev/null +++ b/common/execute/src/lib.rs @@ -0,0 +1,110 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + parse_macro_input, Block, ExprMethodCall, FnArg, Ident, ItemFn, LitStr, ReturnType, Token, + VisPublic, Visibility, +}; + +#[proc_macro_attribute] +pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream { + let f = parse_macro_input!(item as ItemFn); + let target = parse_macro_input!(attr as LitStr).value(); + + let cl = if target == "mixnet" { + quote! {self.mixnet_contract_address()} + } else if target == "vesting" { + quote! {self.vesting_contract_address()} + } else { + panic!("Only `mixnet` and `vesting` targets are supported!") + }; + let cl = proc_macro::TokenStream::from(cl); + let cl = parse_macro_input!(cl as ExprMethodCall); + + let orig_f = f.clone(); + let mut execute_f = f.clone(); + let mut simulate_f = f.clone(); + let name = f.sig.ident; + let name_str = name.to_string(); + let call_args = f.sig.inputs.into_iter().filter_map(|arg| match arg { + FnArg::Receiver(_) => None, + FnArg::Typed(arg) => Some(arg.pat), + }); + let execute_args = call_args.clone(); + let simulate_args = call_args; + + execute_f.sig.asyncness = Some(Token![async](execute_f.sig.ident.span())); + simulate_f.sig.asyncness = Some(Token![async](simulate_f.sig.ident.span())); + + execute_f.vis = Visibility::Public(VisPublic { + pub_token: Token![pub](execute_f.sig.ident.span()), + }); + simulate_f.vis = Visibility::Public(VisPublic { + pub_token: Token![pub](simulate_f.sig.ident.span()), + }); + + execute_f.sig.ident = Ident::new( + &format!("execute{}", execute_f.sig.ident), + execute_f.sig.ident.span(), + ); + + simulate_f.sig.ident = Ident::new( + &format!("simulate{}", simulate_f.sig.ident), + simulate_f.sig.ident.span(), + ); + + let execute_output = quote! { + -> Result + }; + let o_ts = proc_macro::TokenStream::from(execute_output); + execute_f.sig.output = parse_macro_input!(o_ts as ReturnType); + + let simulate_output = quote! { + -> Result + }; + let o_ts = proc_macro::TokenStream::from(simulate_output); + simulate_f.sig.output = parse_macro_input!(o_ts as ReturnType); + + let simulate_block = quote! { + { + let (msg, _fee) = self.#name(#(#simulate_args),*); + let msg = self.wrap_contract_execute_message( + #cl, + &msg, + vec![], + )?; + + self.simulate(vec![msg]).await + } + }; + + let ts = proc_macro::TokenStream::from(simulate_block); + simulate_f.block = Box::new(parse_macro_input!(ts as Block)); + + let execute_block = quote! { + { + let (req, fee) = self.#name(#(#execute_args),*); + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + self.client + .execute( + self.address(), + #cl, + &req, + fee, + #name_str, + vec![], + ) + .await + } + }; + + let ts = proc_macro::TokenStream::from(execute_block); + execute_f.block = Box::new(parse_macro_input!(ts as Block)); + + let out = quote! { + #orig_f + #execute_f + #simulate_f + }; + + out.into() +} diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 72377c7237..074df1422e 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -1537,6 +1537,14 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +[[package]] +name = "execute" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "eyre" version = "0.6.7" @@ -5542,6 +5550,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "cw3", + "execute", "flate2", "futures", "itertools", diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 9f78cef22b..ddb43f08fc 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -62,6 +62,10 @@ fn main() { mixnet::delegate::get_reverse_mix_delegations_paged, mixnet::delegate::undelegate_from_mixnode, mixnet::epoch::get_current_epoch, + mixnet::rewards::claim_delegator_reward, + mixnet::rewards::claim_operator_reward, + mixnet::rewards::compound_operator_reward, + mixnet::rewards::compound_delegator_reward, mixnet::send::send, network_config::add_validator, network_config::get_validator_api_urls, @@ -84,6 +88,10 @@ fn main() { validator_api::status::mixnode_reward_estimation, validator_api::status::mixnode_stake_saturation, validator_api::status::mixnode_status, + vesting::rewards::vesting_claim_delegator_reward, + vesting::rewards::vesting_claim_operator_reward, + vesting::rewards::vesting_compound_operator_reward, + vesting::rewards::vesting_compound_delegator_reward, vesting::bond::vesting_bond_gateway, vesting::bond::vesting_bond_mixnode, vesting::bond::vesting_unbond_gateway, @@ -122,6 +130,14 @@ fn main() { simulate::vesting::simulate_vesting_unbond_mixnode, simulate::vesting::simulate_vesting_update_mixnode, simulate::vesting::simulate_withdraw_vested_coins, + simulate::vesting::simulate_vesting_claim_delegator_reward, + simulate::vesting::simulate_vesting_claim_operator_reward, + simulate::vesting::simulate_vesting_compound_operator_reward, + simulate::vesting::simulate_vesting_compound_delegator_reward, + simulate::mixnet::simulate_claim_delegator_reward, + simulate::mixnet::simulate_claim_operator_reward, + simulate::mixnet::simulate_compound_operator_reward, + simulate::mixnet::simulate_compound_delegator_reward, ]) .menu(Menu::new().add_default_app_submenu_if_macos()) .run(tauri::generate_context!()) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/mod.rs b/nym-wallet/src-tauri/src/operations/mixnet/mod.rs index e59890f38e..40ebc405d8 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/mod.rs @@ -3,4 +3,5 @@ pub mod admin; pub mod bond; pub mod delegate; pub mod epoch; +pub mod rewards; pub mod send; diff --git a/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs new file mode 100644 index 0000000000..adc4612a34 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs @@ -0,0 +1,53 @@ +use crate::error::BackendError; +use crate::nymd_client; +use crate::state::State; +use mixnet_contract_common::IdentityKey; +use std::sync::Arc; +use tokio::sync::RwLock; +use validator_client::nymd::Fee; + +#[tauri::command] +pub async fn claim_operator_reward( + fee: Option, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_claim_operator_reward(fee) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn compound_operator_reward( + fee: Option, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_compound_operator_reward(fee) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn claim_delegator_reward( + mix_identity: IdentityKey, + fee: Option, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_claim_delegator_reward(mix_identity, fee) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn compound_delegator_reward( + mix_identity: IdentityKey, + fee: Option, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_compound_delegator_reward(mix_identity, fee) + .await?; + Ok(()) +} diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index a833f934b1..1e443cd709 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -3,8 +3,10 @@ use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::simulate::{FeeDetails, SimulateResult}; use crate::State; +use mixnet_contract_common::IdentityKey; use mixnet_contract_common::{ExecuteMsg, Gateway, MixNode}; use std::sync::Arc; use tokio::sync::RwLock; @@ -174,3 +176,49 @@ pub async fn simulate_undelegate_from_mixnode( let result = client.nymd.simulate(vec![msg]).await?; Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) } + +#[tauri::command] +pub async fn simulate_claim_operator_reward( + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_claim_operator_reward(None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} + +#[tauri::command] +pub async fn simulate_compound_operator_reward( + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_compound_operator_reward(None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} + +#[tauri::command] +pub async fn simulate_claim_delegator_reward( + mix_identity: IdentityKey, + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_claim_delegator_reward(mix_identity, None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} + +#[tauri::command] +pub async fn simulate_compound_delegator_reward( + mix_identity: IdentityKey, + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_compound_delegator_reward(mix_identity, None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index fe095c5717..616d58f1e5 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -3,8 +3,10 @@ use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::simulate::{FeeDetails, SimulateResult}; use crate::State; +use mixnet_contract_common::IdentityKey; use mixnet_contract_common::{Gateway, MixNode}; use std::sync::Arc; use tokio::sync::RwLock; @@ -152,3 +154,49 @@ pub async fn simulate_withdraw_vested_coins( 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_claim_operator_reward( + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_vesting_claim_operator_reward(None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} + +#[tauri::command] +pub async fn simulate_vesting_compound_operator_reward( + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_vesting_compound_operator_reward(None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} + +#[tauri::command] +pub async fn simulate_vesting_claim_delegator_reward( + mix_identity: IdentityKey, + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_vesting_claim_delegator_reward(mix_identity, None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} + +#[tauri::command] +pub async fn simulate_vesting_compound_delegator_reward( + mix_identity: IdentityKey, + state: tauri::State<'_, Arc>>, +) -> Result { + let result = nymd_client!(state) + .simulate_vesting_compound_delegator_reward(mix_identity, None) + .await?; + let gas_price = nymd_client!(state).gas_price().clone(); + Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee()) +} diff --git a/nym-wallet/src-tauri/src/operations/vesting/mod.rs b/nym-wallet/src-tauri/src/operations/vesting/mod.rs index 3542fd8d5c..f680e5ec6d 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/mod.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/mod.rs @@ -8,6 +8,7 @@ use vesting_contract_common::PledgeData as VestingPledgeData; pub mod bond; pub mod delegate; pub mod queries; +pub mod rewards; #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/pledgedata.ts"))] diff --git a/nym-wallet/src-tauri/src/operations/vesting/rewards.rs b/nym-wallet/src-tauri/src/operations/vesting/rewards.rs new file mode 100644 index 0000000000..e6b27fa78f --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/vesting/rewards.rs @@ -0,0 +1,52 @@ +use crate::error::BackendError; +use crate::nymd_client; +use crate::state::State; +use mixnet_contract_common::IdentityKey; +use std::sync::Arc; +use tokio::sync::RwLock; +use validator_client::nymd::Fee; + +#[tauri::command] +pub async fn vesting_claim_operator_reward( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_vesting_claim_operator_reward(None) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn vesting_compound_operator_reward( + fee: Option, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_vesting_compound_operator_reward(fee) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn vesting_claim_delegator_reward( + mix_identity: IdentityKey, + fee: Option, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_vesting_claim_delegator_reward(mix_identity, fee) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn vesting_compound_delegator_reward( + mix_identity: IdentityKey, + fee: Option, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .execute_vesting_compound_delegator_reward(mix_identity, fee) + .await?; + Ok(()) +} From d8fed178aa7af9bb89516b608df1d547afc94f4e Mon Sep 17 00:00:00 2001 From: vnpmid Date: Mon, 6 Jun 2022 20:38:01 +0700 Subject: [PATCH 24/24] Upgrade to tokio 1.19.1, tokio-util 0.7.3, tokio-stream 0.1.9, tokio-test 0.4.2 (#1305) * Upgrade to tokio 1.19.1, tokio-util 0.7.3, tokio-stream 0.1.9, tokio-test 0.4.2 * Tokio-util 0.7.3 handle_done_delaying, handle_expired_ack_timer * Upgrade to tokio 1.19.1, tokio-util 0.7.3, tokio-stream 0.1.9, tokio-test 0.4.2 --- Cargo.lock | 32 +++++++++---------- clients/client-core/Cargo.toml | 2 +- .../action_controller.rs | 11 ++----- clients/credential/Cargo.toml | 2 +- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- common/client-libs/gateway-client/Cargo.toml | 2 +- common/client-libs/mixnet-client/Cargo.toml | 4 +-- .../client-libs/validator-client/Cargo.toml | 2 +- common/credential-storage/Cargo.toml | 4 +-- common/mixnode-common/Cargo.toml | 4 +-- common/nonexhaustive-delayqueue/Cargo.toml | 6 ++-- common/nymsphinx/Cargo.toml | 2 +- common/nymsphinx/framing/Cargo.toml | 2 +- common/socks5/proxy-helpers/Cargo.toml | 6 ++-- explorer-api/Cargo.toml | 2 +- gateway/Cargo.toml | 8 ++--- mixnode/Cargo.toml | 6 ++-- mixnode/src/node/packet_delayforwarder.rs | 13 +++----- nym-wallet/src-tauri/Cargo.toml | 2 +- .../network-requester/Cargo.toml | 4 +-- validator-api/Cargo.toml | 6 ++-- 22 files changed, 57 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26e5c5b3f9..108ec33301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2149,7 +2149,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.3", "tracing", ] @@ -2879,7 +2879,7 @@ dependencies = [ "log", "nymsphinx", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.3", ] [[package]] @@ -2920,7 +2920,7 @@ dependencies = [ "rand 0.8.5", "serde", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.3", "url", "validator-client", "version-checker", @@ -3010,7 +3010,7 @@ version = "0.1.0" dependencies = [ "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.7.3", ] [[package]] @@ -3145,7 +3145,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.6.9", + "tokio-util 0.7.3", "url", "validator-client", "vergen", @@ -3182,7 +3182,7 @@ dependencies = [ "serde", "serial_test", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.3", "toml", "topology", "url", @@ -3430,7 +3430,7 @@ dependencies = [ "bytes", "nymsphinx-params", "nymsphinx-types", - "tokio-util 0.6.9", + "tokio-util 0.7.3", ] [[package]] @@ -4055,7 +4055,7 @@ dependencies = [ "socks5-requests", "tokio", "tokio-test", - "tokio-util 0.6.9", + "tokio-util 0.7.3", ] [[package]] @@ -5761,9 +5761,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.17.0" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "95eec79ea28c00a365f539f1961e9278fbcaf81c0ff6aaf0e93c181352446948" dependencies = [ "bytes", "libc", @@ -5824,9 +5824,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", "pin-project-lite", @@ -5871,20 +5871,20 @@ dependencies = [ "futures-sink", "log", "pin-project-lite", - "slab", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", + "slab", "tokio", "tracing", ] @@ -5969,7 +5969,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.3", "tower-layer", "tower-service", "tracing", diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 30f88d4c5e..ddd6d14ef9 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -14,7 +14,7 @@ log = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } sled = "0.34" -tokio = { version = "1.4", features = ["macros"] } +tokio = { version = "1.19.1", features = ["macros"] } url = { version ="2.2", features = ["serde"] } # internal diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index d619cf3f35..f8417b0285 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -6,7 +6,7 @@ use crate::client::real_messages_control::acknowledgement_control::Retransmissio use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; use futures::StreamExt; use log::*; -use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey, TimerError}; +use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey}; use nymsphinx::chunking::fragment::FragmentIdentifier; use nymsphinx::Delay as SphinxDelay; use std::collections::HashMap; @@ -209,16 +209,11 @@ impl ActionController { } // note: when the entry expires it's automatically removed from pending_acks_timers - fn handle_expired_ack_timer( - &mut self, - expired_ack: Result, TimerError>, - ) { + fn handle_expired_ack_timer(&mut self, expired_ack: Expired) { // I'm honestly not sure how to handle it, because getting it means other things in our // system are already misbehaving. If we ever see this panic, then I guess we should worry // about it. Perhaps just reschedule it at later point? - let frag_id = expired_ack - .expect("Tokio timer returned an error!") - .into_inner(); + let frag_id = expired_ack.into_inner(); trace!("{} has expired", frag_id); diff --git a/clients/credential/Cargo.toml b/clients/credential/Cargo.toml index 54fdb0ee21..5d2d4d8142 100644 --- a/clients/credential/Cargo.toml +++ b/clients/credential/Cargo.toml @@ -15,7 +15,7 @@ rand = "0.7.3" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" url = "2.2" -tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime +tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime coconut-interface = { path = "../../common/coconut-interface" } credentials = { path = "../../common/credentials" } diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 5c4d0dd6e1..661af318c9 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -27,7 +27,7 @@ pretty_env_logger = "0.4" # for formatting log messages rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization sled = "0.34" # for storage of replySURB decryption keys -tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } # async runtime +tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] } # async runtime tokio-tungstenite = "0.14" # websocket ## internal diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index b5316cc6cd..1ac917a7cf 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -20,7 +20,7 @@ pretty_env_logger = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization snafu = "0.6" -tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } +tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] } url = "2.2" # internal diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 004619e4d3..8e22cd012e 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -35,7 +35,7 @@ default-features = false # non-wasm-only dependencies [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] -version = "1.4" +version = "1.19.1" features = ["macros", "rt", "net", "sync", "time"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite] diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 2585440542..c99ddabbbb 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -9,8 +9,8 @@ edition = "2021" [dependencies] futures = "0.3" log = "0.4.8" -tokio = { version = "1.4", features = ["time", "net", "rt"] } -tokio-util = { version = "0.6", features = ["codec"] } +tokio = { version = "1.19.1", features = ["time", "net", "rt"] } +tokio-util = { version = "0.7.3", features = ["codec"] } # internal nymsphinx = {path = "../../nymsphinx" } diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 6c9fa9faf5..e69699d36b 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -22,7 +22,7 @@ reqwest = { version = "0.11", features = ["json"] } thiserror = "1" log = "0.4" url = { version = "2.2", features = ["serde"] } -tokio = { version = "1.10", features = ["sync", "time"] } +tokio = { version = "1.19.1", features = ["sync", "time"] } futures = "0.3" coconut-interface = { path = "../../coconut-interface" } diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index fe05fb4bd3..70d18c4716 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -12,9 +12,9 @@ nymcoconut = { path = "../nymcoconut" } log = "0.4" sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]} thiserror = "1.0" -tokio = { version = "1.4", features = [ "rt-multi-thread", "net", "signal", "fs" ] } +tokio = { version = "1.19.1", features = [ "rt-multi-thread", "net", "signal", "fs" ] } [build-dependencies] sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } -tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] } \ No newline at end of file +tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros"] } \ No newline at end of file diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index f5b70e1612..77c25d0455 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -14,8 +14,8 @@ humantime-serde = "1.0" log = "0.4" rand = "0.8" serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.4", features = ["time", "macros", "rt", "net", "io-util"] } -tokio-util = { version = "0.6", features = ["codec"] } +tokio = { version = "1.19.1", features = ["time", "macros", "rt", "net", "io-util"] } +tokio-util = { version = "0.7.3", features = ["codec"] } url = "2.2" crypto = { path = "../crypto" } diff --git a/common/nonexhaustive-delayqueue/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml index ea64ddf6c6..778342e578 100644 --- a/common/nonexhaustive-delayqueue/Cargo.toml +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.4", features = [] } -tokio-stream = "0.1" # this one seems to be a thing until `Stream` trait is stabilised in stdlib -tokio-util = { version = "0.6", features = ["time"] } +tokio = { version = "1.19.1", features = [] } +tokio-stream = "0.1.9" # this one seems to be a thing until `Stream` trait is stabilised in stdlib +tokio-util = { version = "0.7.3", features = ["time"] } diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index 460fd11390..b697700ec1 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -33,5 +33,5 @@ mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" path = "framing" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] -version = "1.4" +version = "1.19.1" features = ["sync"] diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index 0cddb5bf22..6921a8cf69 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] bytes = "1.0" -tokio-util = { version = "0.6", features = ["codec"] } +tokio-util = { version = "0.7.3", features = ["codec"] } nymsphinx-types = { path = "../types" } nymsphinx-params = { path = "../params" } diff --git a/common/socks5/proxy-helpers/Cargo.toml b/common/socks5/proxy-helpers/Cargo.toml index 77c9e4cf7e..71a4cebb23 100644 --- a/common/socks5/proxy-helpers/Cargo.toml +++ b/common/socks5/proxy-helpers/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" [dependencies] bytes = "1.0" -tokio = { version = "1.4", features = [ "net", "io-util", "sync", "macros", "time", "rt-multi-thread" ] } -tokio-util = { version = "0.6", features = [ "io" ] } # reason for getting this guy is to to able to port to tokio 1.X more quickly by being able to use +tokio = { version = "1.19.1", features = [ "net", "io-util", "sync", "macros", "time", "rt-multi-thread" ] } +tokio-util = { version = "0.7.3", features = [ "io" ] } # reason for getting this guy is to to able to port to tokio 1.X more quickly by being able to use # their `read_buf` [from the util crate] replacement rather than having to rethink/reimplement `AvailableReader` with the new AsyncRead trait definition. # In the long run, the dependency should probably get removed in favour of pure-tokio implementation, but for time being it's fine. futures = "0.3" @@ -18,4 +18,4 @@ socks5-requests = { path = "../requests" } ordered-buffer = { path = "../ordered-buffer" } [dev-dependencies] -tokio-test = "0.4" \ No newline at end of file +tokio-test = "0.4.2" \ No newline at end of file diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index a4787ecebc..58096adee4 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -21,7 +21,7 @@ schemars = { version = "0.8", features = ["preserve_order"] } serde = "1.0.126" serde_json = "1.0.66" thiserror = "1.0.29" -tokio = {version = "1.9.0", features = ["full"] } +tokio = {version = "1.19.1", features = ["full"] } mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } network-defaults = { path = "../common/network-defaults" } diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index e25a803d14..e3ab0f4722 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -31,10 +31,10 @@ serde = { version = "1.0.104", features = ["derive"] } sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } subtle-encoding = { version = "0.5", features = ["bech32-preview"]} thiserror = "1" -tokio = { version = "1.4", features = [ "rt-multi-thread", "net", "signal", "fs" ] } -tokio-stream = { version = "0.1", features = [ "fs" ] } +tokio = { version = "1.19.1", features = [ "rt-multi-thread", "net", "signal", "fs" ] } +tokio-stream = { version = "0.1.9", features = [ "fs" ] } tokio-tungstenite = "0.14" -tokio-util = { version = "0.6", features = [ "codec" ] } +tokio-util = { version = "0.7.3", features = [ "codec" ] } url = { version = "2.2", features = [ "serde" ] } web3 = "0.17.0" @@ -59,6 +59,6 @@ coconut = ["coconut-interface", "gateway-requests/coconut", "gateway-client/coco eth = [] [build-dependencies] -tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros"] } sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] } diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 9dac6d1953..e7157683a6 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -28,8 +28,8 @@ pretty_env_logger = "0.4.0" rand = "0.7.3" rocket = { version="0.5.0-rc.1", features = ["json"] } serde = { version="1.0", features = ["derive"] } -tokio = { version="1.8", features = ["rt-multi-thread", "net", "signal"] } -tokio-util = { version="0.6.7", features = ["codec"] } +tokio = { version="1.19.1", features = ["rt-multi-thread", "net", "signal"] } +tokio-util = { version="0.7.3", features = ["codec"] } toml = "0.5.8" url = { version = "2.2", features = ["serde"] } lazy_static = "1.4.0" @@ -49,7 +49,7 @@ version-checker = { path="../common/version-checker" } [dev-dependencies] serial_test = "0.5" -tokio = { version="1.8", features = ["rt-multi-thread", "net", "signal", "test-util"] } +tokio = { version="1.19.1", features = ["rt-multi-thread", "net", "signal", "test-util"] } nymsphinx-types = { path = "../common/nymsphinx/types" } nymsphinx-params = { path = "../common/nymsphinx/params" } diff --git a/mixnode/src/node/packet_delayforwarder.rs b/mixnode/src/node/packet_delayforwarder.rs index d2f1ced206..e0aece8fcb 100644 --- a/mixnode/src/node/packet_delayforwarder.rs +++ b/mixnode/src/node/packet_delayforwarder.rs @@ -4,7 +4,7 @@ use crate::node::node_statistics::UpdateSender; use futures::channel::mpsc; use futures::StreamExt; -use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, TimerError}; +use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue}; use nymsphinx::forwarding::packet::MixPacket; use std::io; use tokio::time::Instant; @@ -75,13 +75,8 @@ where } /// Upon packet being finished getting delayed, forward it to the mixnet. - fn handle_done_delaying(&mut self, packet: Option, TimerError>>) { - // those are critical errors that I don't think can be recovered from. - let delayed = packet.expect("the queue has unexpectedly terminated!"); - let delayed_packet = delayed - .expect("Encountered timer issue within the runtime!") - .into_inner(); - + fn handle_done_delaying(&mut self, packet: Expired) { + let delayed_packet = packet.into_inner(); self.forward_packet(delayed_packet) } @@ -105,7 +100,7 @@ where loop { tokio::select! { delayed = self.delay_queue.next() => { - self.handle_done_delaying(delayed); + self.handle_done_delaying(delayed.unwrap()); } new_packet = self.packet_receiver.next() => { // this one is impossible to ever panic - the object itself contains a sender diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 82f6e2d89b..03a23fe886 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -38,7 +38,7 @@ strum = { version = "0.23", features = ["derive"] } tauri = { version = "=1.0.0-rc.2", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] } tendermint-rpc = "0.23.0" thiserror = "1.0" -tokio = { version = "1.10", features = ["sync", "time"] } +tokio = { version = "1.19.1", features = ["sync", "time"] } toml = "0.5.8" url = "2.2" diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index bd266ecd9a..ba83a25179 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -23,7 +23,7 @@ rocket = { version = "0.5.0-rc.1", features = ["json"], optional = true } serde = { version = "1.0", features = ["derive"] } sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "chrono"]} thiserror = "1" -tokio = { version = "1.4", features = [ "net", "rt-multi-thread", "macros", "time" ] } +tokio = { version = "1.19.1", features = [ "net", "rt-multi-thread", "macros", "time" ] } tokio-tungstenite = "0.14" @@ -40,7 +40,7 @@ rand = "0.7" [build-dependencies] sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } -tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros"] } [features] stats-service = ["rocket"] \ No newline at end of file diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index c33e43b55d..a75534348a 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -33,8 +33,8 @@ serde = "1.0" serde_json = "1.0" thiserror = "1" time = { version = "0.3", features = ["serde-human-readable", "parsing"]} -tokio = { version = "1.4", features = ["rt-multi-thread", "macros", "signal", "time"] } -tokio-stream = "0.1.8" +tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros", "signal", "time"] } +tokio-stream = "0.1.9" url = "2.2" anyhow = "1" @@ -72,7 +72,7 @@ coconut = ["coconut-interface", "credentials", "gateway-client/coconut", "creden no-reward = [] [build-dependencies] -tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros"] } sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }