From 305864aa0fececc0ac255a1a563f0b31dd4e2ebd Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 14 Dec 2021 12:25:48 +0100 Subject: [PATCH] Feature/vesting to wallet (#954) * Better code structure, proper error handling, VestingSigningClient for wallet * Add vesting contract queries to wallet * Address review comments Co-authored-by: Drazen Urch --- Makefile | 4 - .../nymd/cosmwasm_client/signing_client.rs | 2 +- .../src/nymd/traits/vesting_query_client.rs | 4 +- .../src/nymd/traits/vesting_signing_client.rs | 40 ++--- .../validator-client/src/nymd/wallet/mod.rs | 4 +- nym-wallet/Cargo.lock | 1 + nym-wallet/src-tauri/Cargo.toml | 1 + nym-wallet/src-tauri/src/coin.rs | 24 ++- nym-wallet/src-tauri/src/error.rs | 32 +++- nym-wallet/src-tauri/src/main.rs | 85 +++++----- nym-wallet/src-tauri/src/operations/bond.rs | 66 -------- .../src-tauri/src/operations/delegate.rs | 68 -------- .../src/operations/{ => mixnet}/account.rs | 43 ++---- .../src/operations/{ => mixnet}/admin.rs | 32 ++-- .../src-tauri/src/operations/mixnet/bond.rs | 50 ++++++ .../src/operations/mixnet/delegate.rs | 51 ++++++ .../src-tauri/src/operations/mixnet/mod.rs | 5 + .../src/operations/{ => mixnet}/send.rs | 39 ++--- nym-wallet/src-tauri/src/operations/mod.rs | 7 +- .../src-tauri/src/operations/vesting/bond.rs | 51 ++++++ .../src/operations/vesting/delegate.rs | 42 +++++ .../src-tauri/src/operations/vesting/mod.rs | 3 + .../src/operations/vesting/queries.rs | 146 ++++++++++++++++++ nym-wallet/src-tauri/src/state.rs | 17 +- nym-wallet/src-tauri/src/utils.rs | 69 ++++++--- 25 files changed, 561 insertions(+), 325 deletions(-) delete mode 100644 nym-wallet/src-tauri/src/operations/bond.rs delete mode 100644 nym-wallet/src-tauri/src/operations/delegate.rs rename nym-wallet/src-tauri/src/operations/{ => mixnet}/account.rs (69%) rename nym-wallet/src-tauri/src/operations/{ => mixnet}/admin.rs (70%) create mode 100644 nym-wallet/src-tauri/src/operations/mixnet/bond.rs create mode 100644 nym-wallet/src-tauri/src/operations/mixnet/delegate.rs create mode 100644 nym-wallet/src-tauri/src/operations/mixnet/mod.rs rename nym-wallet/src-tauri/src/operations/{ => mixnet}/send.rs (62%) create mode 100644 nym-wallet/src-tauri/src/operations/vesting/bond.rs create mode 100644 nym-wallet/src-tauri/src/operations/vesting/delegate.rs create mode 100644 nym-wallet/src-tauri/src/operations/vesting/mod.rs create mode 100644 nym-wallet/src-tauri/src/operations/vesting/queries.rs diff --git a/Makefile b/Makefile index cff68e22df..24f2606676 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,3 @@ fmt-contracts: fmt-wallet: cargo fmt --manifest-path nym-wallet/Cargo.toml --all - - - - 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 52c3c7fcf1..977b5a75f1 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 @@ -636,7 +636,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Client { rpc_client: HttpClient, signer: DirectSecp256k1HdWallet, 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 7eb5badaae..c2d91a25ce 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 @@ -52,8 +52,8 @@ pub trait VestingQueryClient { async fn delegated_vesting( &self, - block_time: Option, vesting_account_address: &str, + block_time: Option, ) -> Result; } @@ -162,8 +162,8 @@ impl VestingQueryClient for NymdClient { async fn delegated_vesting( &self, - block_time: Option, vesting_account_address: &str, + block_time: Option, ) -> Result { let request = VestingQueryMsg::GetDelegatedVesting { vesting_account_address: vesting_account_address.to_string(), 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 42f849ed54..37b1d750b6 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 @@ -8,7 +8,7 @@ use crate::nymd::fee::helpers::Operation; use crate::nymd::{cosmwasm_coin_to_cosmos_coin, NymdClient}; use async_trait::async_trait; use cosmwasm_std::Coin; -use mixnet_contract::{Gateway, IdentityKey, MixNode}; +use mixnet_contract::{Gateway, IdentityKey, IdentityKeyRef, MixNode}; use vesting_contract::messages::ExecuteMsg as VestingExecuteMsg; #[async_trait] @@ -16,8 +16,8 @@ pub trait VestingSigningClient { async fn vesting_bond_gateway( &self, gateway: Gateway, - pledge: Coin, owner_signature: &str, + pledge: Coin, ) -> Result; async fn vesting_unbond_gateway(&self) -> Result; @@ -31,8 +31,8 @@ pub trait VestingSigningClient { async fn vesting_bond_mixnode( &self, mix_node: MixNode, - pledge: Coin, owner_signature: &str, + pledge: Coin, ) -> Result; async fn vesting_unbond_mixnode(&self) -> Result; @@ -51,15 +51,15 @@ pub trait VestingSigningClient { amount: Coin, ) -> Result; - async fn vesting_delegate_to_mixnode( + async fn vesting_delegate_to_mixnode<'a>( &self, - mix_identity: IdentityKey, - amount: Coin, + mix_identity: IdentityKeyRef<'a>, + amount: &Coin, ) -> Result; - async fn vesting_undelegate_from_mixnode( + async fn vesting_undelegate_from_mixnode<'a>( &self, - mix_identity: IdentityKey, + mix_identity: IdentityKeyRef<'a>, ) -> Result; async fn create_periodic_vesting_account( @@ -75,8 +75,8 @@ impl VestingSigningClient for NymdClient async fn vesting_bond_gateway( &self, gateway: Gateway, - pledge: Coin, owner_signature: &str, + pledge: Coin, ) -> Result { let fee = self.operation_fee(Operation::BondGateway); let req = VestingExecuteMsg::BondGateway { @@ -135,8 +135,8 @@ impl VestingSigningClient for NymdClient async fn vesting_bond_mixnode( &self, mix_node: MixNode, - pledge: Coin, owner_signature: &str, + pledge: Coin, ) -> Result { let fee = self.operation_fee(Operation::BondMixnode); let req = VestingExecuteMsg::BondMixnode { @@ -230,13 +230,15 @@ impl VestingSigningClient for NymdClient ) .await } - async fn vesting_delegate_to_mixnode( + async fn vesting_delegate_to_mixnode<'a>( &self, - mix_identity: IdentityKey, - amount: Coin, + mix_identity: IdentityKeyRef<'a>, + amount: &Coin, ) -> Result { let fee = self.operation_fee(Operation::DelegateToMixnode); - let req = VestingExecuteMsg::DelegateToMixnode { mix_identity }; + let req = VestingExecuteMsg::DelegateToMixnode { + mix_identity: mix_identity.into(), + }; self.client .execute( self.address(), @@ -244,16 +246,18 @@ impl VestingSigningClient for NymdClient &req, fee, "VestingContract::DeledateToMixnode", - vec![cosmwasm_coin_to_cosmos_coin(amount)], + vec![cosmwasm_coin_to_cosmos_coin(amount.to_owned())], ) .await } - async fn vesting_undelegate_from_mixnode( + async fn vesting_undelegate_from_mixnode<'a>( &self, - mix_identity: IdentityKey, + mix_identity: IdentityKeyRef<'a>, ) -> Result { let fee = self.operation_fee(Operation::UndelegateFromMixnode); - let req = VestingExecuteMsg::UndelegateFromMixnode { mix_identity }; + let req = VestingExecuteMsg::UndelegateFromMixnode { + mix_identity: mix_identity.into(), + }; self.client .execute( self.address(), diff --git a/common/client-libs/validator-client/src/nymd/wallet/mod.rs b/common/client-libs/validator-client/src/nymd/wallet/mod.rs index cd07361e79..8617bf9c4f 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nymd/wallet/mod.rs @@ -10,7 +10,7 @@ use cosmrs::tx::SignDoc; use cosmrs::{tx, AccountId}; /// Derivation information required to derive a keypair and an address from a mnemonic. -#[derive(Debug)] +#[derive(Debug, Clone)] struct Secp256k1Derivation { hd_path: DerivationPath, prefix: String, @@ -40,7 +40,7 @@ impl AccountData { type Secp256k1Keypair = (SigningKey, PublicKey); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DirectSecp256k1HdWallet { /// Base secret secret: bip39::Mnemonic, diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index e79709720d..34eb903b5e 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -2709,6 +2709,7 @@ dependencies = [ "cosmwasm-std", "credentials", "dirs", + "eyre", "mixnet-contract", "rand 0.6.5", "serde", diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 35d05c702b..60d6f38b13 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -26,6 +26,7 @@ thiserror = "1.0" tendermint-rpc = "0.23.0" url = "2.0" rand = "0.6.5" +eyre = "0.6.5" cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"] } diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index 3b2bf5931f..ecfe2faa4f 100644 --- a/nym-wallet/src-tauri/src/coin.rs +++ b/nym-wallet/src-tauri/src/coin.rs @@ -1,5 +1,6 @@ // This should be moved out of the wallet, and used as a primary coin type throughout the codebase +use crate::error::BackendError; use ::config::defaults::DENOM; use cosmrs::Decimal; use cosmrs::Denom as CosmosDenom; @@ -12,8 +13,6 @@ use std::ops::{Add, Sub}; use std::str::FromStr; use validator_client::nymd::{CosmosCoin, GasPrice}; -use crate::format_err; - #[cfg_attr(test, derive(ts_rs::TS))] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum Denom { @@ -33,19 +32,16 @@ impl fmt::Display for Denom { } impl FromStr for Denom { - type Err = String; + type Err = BackendError; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { let s = s.to_lowercase(); if s == DENOM.to_lowercase() || s == "minor" { Ok(Denom::Minor) } else if s == DENOM[1..].to_lowercase() || s == "major" { Ok(Denom::Major) } else { - Err(format_err!(format!( - "{} is not a valid denomination string", - s - ))) + Err(BackendError::InvalidDenom(s)) } } } @@ -169,9 +165,9 @@ impl Coin { } impl TryFrom for CosmWasmCoin { - type Error = String; + type Error = BackendError; - fn try_from(coin: Coin) -> Result { + fn try_from(coin: Coin) -> Result { Ok(CosmWasmCoin::new( Uint128::try_from(coin.amount.as_str()).unwrap().u128(), coin.denom.to_string(), @@ -180,15 +176,15 @@ impl TryFrom for CosmWasmCoin { } impl TryFrom for CosmosCoin { - type Error = String; + type Error = BackendError; - fn try_from(coin: Coin) -> Result { + fn try_from(coin: Coin) -> Result { match Decimal::from_str(&coin.amount) { Ok(d) => Ok(CosmosCoin { amount: d, - denom: CosmosDenom::from_str(&coin.denom.to_string()).unwrap(), + denom: CosmosDenom::from_str(&coin.denom.to_string())?, }), - Err(e) => Err(format_err!(e)), + Err(e) => Err(e.into()), } } } diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 2031829a9a..605cc78047 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -1,21 +1,47 @@ +use serde::{Serialize, Serializer}; use thiserror::Error; use validator_client::nymd::error::NymdError; #[derive(Error, Debug)] pub enum BackendError { - #[error("Error parsing bip39 mnemonic")] + #[error("{source}")] Bip39Error { #[from] source: bip39::Error, }, - #[error("Error parsing into tendermint Url")] + #[error("{source}")] TendermintError { #[from] source: tendermint_rpc::Error, }, - #[error("Error getting balances")] + #[error("{source}")] NymdError { #[from] source: NymdError, }, + #[error("{source}")] + CosmwasmStd { + #[from] + source: cosmwasm_std::StdError, + }, + #[error("{source}")] + ErrorReport { + #[from] + source: eyre::Report, + }, + #[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), +} + +impl Serialize for BackendError { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } } diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 3d996c99ca..2dc6b40e53 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -18,47 +18,48 @@ mod state; mod utils; use crate::menu::AddDefaultSubmenus; -use crate::operations::account::*; -use crate::operations::admin::*; -use crate::operations::bond::*; -use crate::operations::delegate::*; -use crate::operations::send::*; -use crate::utils::*; +use crate::operations::mixnet; +use crate::operations::vesting; use crate::state::State; -#[cfg(test)] -use crate::coin::{Coin, Denom}; - -#[macro_export] -macro_rules! format_err { - ($e:expr) => { - format!("line {}: {}", line!(), $e) - }; -} - fn main() { tauri::Builder::default() .manage(Arc::new(RwLock::new(State::default()))) .invoke_handler(tauri::generate_handler![ - connect_with_mnemonic, - get_balance, - minor_to_major, - major_to_minor, - owns_gateway, - owns_mixnode, - bond_mixnode, - unbond_mixnode, - bond_gateway, - unbond_gateway, - delegate_to_mixnode, - undelegate_from_mixnode, - send, - create_new_account, - get_approximate_fee, - get_contract_settings, - update_contract_settings, - get_reverse_mix_delegations_paged, + mixnet::account::connect_with_mnemonic, + mixnet::account::create_new_account, + mixnet::account::get_balance, + mixnet::admin::get_contract_settings, + mixnet::admin::update_contract_settings, + mixnet::bond::bond_gateway, + mixnet::bond::bond_mixnode, + mixnet::bond::unbond_gateway, + mixnet::bond::unbond_mixnode, + mixnet::delegate::delegate_to_mixnode, + mixnet::delegate::get_reverse_mix_delegations_paged, + mixnet::delegate::undelegate_from_mixnode, + mixnet::send::send, + utils::get_approximate_fee, + utils::major_to_minor, + utils::minor_to_major, + utils::owns_gateway, + utils::owns_mixnode, + vesting::bond::vesting_bond_gateway, + vesting::bond::vesting_bond_mixnode, + vesting::bond::vesting_unbond_gateway, + vesting::bond::vesting_unbond_mixnode, + vesting::delegate::vesting_delegate_to_mixnode, + vesting::delegate::vesting_undelegate_from_mixnode, + vesting::queries::locked_coins, + vesting::queries::spendable_coins, + vesting::queries::vesting_coins, + vesting::queries::vested_coins, + vesting::queries::vesting_start_time, + vesting::queries::vesting_end_time, + vesting::queries::original_vesting, + vesting::queries::delegated_free, + vesting::queries::delegated_vesting, ]) .menu(Menu::new().add_default_app_submenu_if_macos()) .run(tauri::generate_context!()) @@ -69,15 +70,15 @@ fn main() { mod test { ts_rs::export! { mixnet_contract::MixNode => "../src/types/rust/mixnode.ts", - crate::Coin => "../src/types/rust/coin.ts", - crate::Balance => "../src/types/rust/balance.ts", + crate::coin::Coin => "../src/types/rust/coin.ts", + crate::mixnet::account::Balance => "../src/types/rust/balance.ts", mixnet_contract::Gateway => "../src/types/rust/gateway.ts", - crate::TauriTxResult => "../src/types/rust/tauritxresult.ts", - crate::TransactionDetails => "../src/types/rust/transactiondetails.ts", + crate::mixnet::send::TauriTxResult => "../src/types/rust/tauritxresult.ts", + crate::mixnet::send::TransactionDetails => "../src/types/rust/transactiondetails.ts", validator_client::nymd::fee::helpers::Operation => "../src/types/rust/operation.ts", - crate::Denom => "../src/types/rust/denom.ts", - crate::DelegationResult => "../src/types/rust/delegationresult.ts", - crate::Account => "../src/types/rust/account.ts", - crate::TauriContractStateParams => "../src/types/rust/stateparams.ts" + crate::coin::Denom => "../src/types/rust/denom.ts", + crate::utils::DelegationResult => "../src/types/rust/delegationresult.ts", + crate::mixnet::account::Account => "../src/types/rust/account.ts", + crate::mixnet::admin::TauriContractStateParams => "../src/types/rust/stateparams.ts" } } diff --git a/nym-wallet/src-tauri/src/operations/bond.rs b/nym-wallet/src-tauri/src/operations/bond.rs deleted file mode 100644 index 3cbd12b5ac..0000000000 --- a/nym-wallet/src-tauri/src/operations/bond.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::coin::Coin; -use crate::format_err; -use crate::state::State; -use crate::{Gateway, MixNode}; -use cosmwasm_std::Coin as CosmWasmCoin; -use std::convert::TryInto; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[tauri::command] -pub async fn bond_gateway( - gateway: Gateway, - pledge: Coin, - owner_signature: String, - state: tauri::State<'_, Arc>>, -) -> Result<(), String> { - let r_state = state.read().await; - let pledge: CosmWasmCoin = match pledge.try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let client = r_state.client()?; - match client.bond_gateway(gateway, owner_signature, pledge).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -pub async fn unbond_gateway(state: tauri::State<'_, Arc>>) -> Result<(), String> { - let r_state = state.read().await; - let client = r_state.client()?; - match client.unbond_gateway().await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -pub async fn unbond_mixnode(state: tauri::State<'_, Arc>>) -> Result<(), String> { - let r_state = state.read().await; - let client = r_state.client()?; - match client.unbond_mixnode().await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -pub async fn bond_mixnode( - mixnode: MixNode, - owner_signature: String, - pledge: Coin, - state: tauri::State<'_, Arc>>, -) -> Result<(), String> { - let r_state = state.read().await; - let pledge: CosmWasmCoin = match pledge.try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let client = r_state.client()?; - match client.bond_mixnode(mixnode, owner_signature, pledge).await { - Ok(_result) => Ok(()), - Err(e) => Err(format_err!(e)), - } -} diff --git a/nym-wallet/src-tauri/src/operations/delegate.rs b/nym-wallet/src-tauri/src/operations/delegate.rs deleted file mode 100644 index 06aa83f179..0000000000 --- a/nym-wallet/src-tauri/src/operations/delegate.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::coin::Coin; -use crate::format_err; -use crate::state::State; -use cosmwasm_std::Coin as CosmWasmCoin; -use mixnet_contract::PagedDelegatorDelegationsResponse; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Serialize, Deserialize)] -pub struct DelegationResult { - source_address: String, - target_address: String, - amount: Option, -} - -#[tauri::command] -pub async fn delegate_to_mixnode( - identity: &str, - amount: Coin, - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let delegation: CosmWasmCoin = match amount.try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let client = r_state.client()?; - match client.delegate_to_mixnode(identity, &delegation).await { - Ok(_result) => Ok(DelegationResult { - source_address: client.address().to_string(), - target_address: identity.to_string(), - amount: Some(delegation.into()), - }), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -pub async fn undelegate_from_mixnode( - identity: &str, - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.remove_mixnode_delegation(identity).await { - Ok(_result) => Ok(DelegationResult { - source_address: client.address().to_string(), - target_address: identity.to_string(), - amount: None, - }), - Err(e) => Err(format_err!(e)), - } -} - -#[tauri::command] -pub async fn get_reverse_mix_delegations_paged( - state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - client - .get_delegator_delegations_paged(client.address().to_string(), None, None) - .await - .map_err(|err| format_err!(err)) -} diff --git a/nym-wallet/src-tauri/src/operations/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs similarity index 69% rename from nym-wallet/src-tauri/src/operations/account.rs rename to nym-wallet/src-tauri/src/operations/mixnet/account.rs index 03b3b104e3..3e1f019c54 100644 --- a/nym-wallet/src-tauri/src/operations/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -1,7 +1,7 @@ +use crate::client; use crate::coin::{Coin, Denom}; use crate::config::Config; use crate::error::BackendError; -use crate::format_err; use crate::state::State; use bip39::{Language, Mnemonic}; use serde::{Deserialize, Serialize}; @@ -30,26 +30,16 @@ pub struct Balance { pub async fn connect_with_mnemonic( mnemonic: String, state: tauri::State<'_, Arc>>, -) -> Result { - let mnemonic = match Mnemonic::from_str(&mnemonic) { - Ok(mnemonic) => mnemonic, - Err(e) => return Err(BackendError::from(e).to_string()), - }; - let client; - { +) -> Result { + let mnemonic = Mnemonic::from_str(&mnemonic)?; + let client = { let r_state = state.read().await; - client = _connect_with_mnemonic(mnemonic, &r_state.config()); - } + _connect_with_mnemonic(mnemonic, &r_state.config()) + }; - let contract_address = match client.mixnet_contract_address() { - Ok(address) => address.to_string(), - Err(e) => return Err(format_err!(e)), - }; + let contract_address = client.mixnet_contract_address()?.to_string(); let client_address = client.address().to_string(); - let denom = match client.denom() { - Ok(denom) => denom, - Err(e) => return Err(format_err!(e)), - }; + let denom = client.denom()?; let account = Account { contract_address, @@ -65,10 +55,10 @@ pub async fn connect_with_mnemonic( } #[tauri::command] -pub async fn get_balance(state: tauri::State<'_, Arc>>) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.get_balance(client.address()).await { +pub async fn get_balance( + state: tauri::State<'_, Arc>>, +) -> Result { + match client!(state).get_balance(client!(state).address()).await { Ok(Some(coin)) => { let coin = Coin::new( &coin.amount.to_string(), @@ -79,18 +69,17 @@ pub async fn get_balance(state: tauri::State<'_, Arc>>) -> Result< printable_balance: coin.to_major().to_string(), }) } - Ok(None) => Err(format!( - "No balance available for address {}", - client.address() + Ok(None) => Err(BackendError::NoBalance( + client!(state).address().to_string(), )), - Err(e) => Err(BackendError::from(e).to_string()), + Err(e) => Err(BackendError::from(e)), } } #[tauri::command] pub async fn create_new_account( state: tauri::State<'_, Arc>>, -) -> Result { +) -> Result { let rand_mnemonic = random_mnemonic(); let mut client = connect_with_mnemonic(rand_mnemonic.to_string(), state).await?; client.mnemonic = Some(rand_mnemonic.to_string()); diff --git a/nym-wallet/src-tauri/src/operations/admin.rs b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs similarity index 70% rename from nym-wallet/src-tauri/src/operations/admin.rs rename to nym-wallet/src-tauri/src/operations/mixnet/admin.rs index 51265fbf2f..961a57b4e7 100644 --- a/nym-wallet/src-tauri/src/operations/admin.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs @@ -1,4 +1,5 @@ -use crate::format_err; +use crate::client; +use crate::error::BackendError; use crate::state::State; use cosmwasm_std::Uint128; use mixnet_contract::ContractStateParams; @@ -30,7 +31,7 @@ impl From for TauriContractStateParams { } impl TryFrom for ContractStateParams { - type Error = Box; + type Error = BackendError; fn try_from(p: TauriContractStateParams) -> Result { Ok(ContractStateParams { @@ -46,31 +47,18 @@ impl TryFrom for ContractStateParams { #[tauri::command] pub async fn get_contract_settings( state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.get_contract_settings().await { - Ok(params) => Ok(params.into()), - Err(e) => Err(format_err!(e)), - } +) -> Result { + Ok(client!(state).get_contract_settings().await?.into()) } #[tauri::command] pub async fn update_contract_settings( params: TauriContractStateParams, state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - let mixnet_contract_settings_params: ContractStateParams = match params.try_into() { - Ok(mixnet_contract_settings_params) => mixnet_contract_settings_params, - Err(e) => return Err(format_err!(e)), - }; - match client +) -> Result { + let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; + client!(state) .update_contract_settings(mixnet_contract_settings_params.clone()) - .await - { - Ok(_) => Ok(mixnet_contract_settings_params.into()), - Err(e) => Err(format_err!(e)), - } + .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 new file mode 100644 index 0000000000..0fa8519236 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -0,0 +1,50 @@ +use crate::client; +use crate::coin::Coin; +use crate::error::BackendError; +use crate::state::State; +use crate::{Gateway, MixNode}; +use std::convert::TryInto; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[tauri::command] +pub async fn bond_gateway( + gateway: Gateway, + pledge: Coin, + owner_signature: String, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state) + .bond_gateway(gateway, owner_signature, pledge.try_into()?) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn unbond_gateway( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state).unbond_gateway().await?; + Ok(()) +} + +#[tauri::command] +pub async fn unbond_mixnode( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state).unbond_mixnode().await?; + Ok(()) +} + +#[tauri::command] +pub async fn bond_mixnode( + mixnode: MixNode, + owner_signature: String, + pledge: Coin, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state) + .bond_mixnode(mixnode, owner_signature, pledge.try_into()?) + .await?; + Ok(()) +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs new file mode 100644 index 0000000000..1fb96308e4 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -0,0 +1,51 @@ +use crate::client; +use crate::coin::Coin; +use crate::error::BackendError; +use crate::state::State; +use crate::utils::DelegationResult; +use cosmwasm_std::Coin as CosmWasmCoin; +use mixnet_contract::PagedDelegatorDelegationsResponse; +use std::convert::TryInto; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[tauri::command] +pub async fn delegate_to_mixnode( + identity: &str, + amount: Coin, + state: tauri::State<'_, Arc>>, +) -> Result { + let delegation: CosmWasmCoin = amount.try_into()?; + client!(state) + .delegate_to_mixnode(identity, &delegation) + .await?; + Ok(DelegationResult::new( + &client!(state).address().to_string(), + identity, + Some(delegation.into()), + )) +} + +#[tauri::command] +pub async fn undelegate_from_mixnode( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + client!(state).remove_mixnode_delegation(identity).await?; + Ok(DelegationResult::new( + &client!(state).address().to_string(), + identity, + None, + )) +} + +#[tauri::command] +pub async fn get_reverse_mix_delegations_paged( + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + client!(state) + .get_delegator_delegations_paged(client!(state).address().to_string(), None, None) + .await?, + ) +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/mod.rs b/nym-wallet/src-tauri/src/operations/mixnet/mod.rs new file mode 100644 index 0000000000..47f4788d6c --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/mixnet/mod.rs @@ -0,0 +1,5 @@ +pub mod account; +pub mod admin; +pub mod bond; +pub mod delegate; +pub mod send; diff --git a/nym-wallet/src-tauri/src/operations/send.rs b/nym-wallet/src-tauri/src/operations/mixnet/send.rs similarity index 62% rename from nym-wallet/src-tauri/src/operations/send.rs rename to nym-wallet/src-tauri/src/operations/mixnet/send.rs index 444302ca56..417aa42177 100644 --- a/nym-wallet/src-tauri/src/operations/send.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/send.rs @@ -1,5 +1,6 @@ +use crate::client; use crate::coin::Coin; -use crate::format_err; +use crate::error::BackendError; use crate::state::State; use serde::{Deserialize, Serialize}; use std::convert::TryInto; @@ -47,26 +48,18 @@ pub async fn send( amount: Coin, memo: String, state: tauri::State<'_, Arc>>, -) -> Result { - let address = match AccountId::from_str(address) { - Ok(addy) => addy, - Err(e) => return Err(format_err!(e)), - }; - let cosmos_amount: CosmosCoin = match amount.clone().try_into() { - Ok(b) => b, - Err(e) => return Err(format_err!(e)), - }; - let r_state = state.read().await; - let client = r_state.client()?; - match client.send(&address, vec![cosmos_amount], memo).await { - Ok(result) => Ok(TauriTxResult::new( - result, - TransactionDetails { - from_address: client.address().to_string(), - to_address: address.to_string(), - amount, - }, - )), - Err(e) => Err(format_err!(e)), - } +) -> Result { + let address = AccountId::from_str(address)?; + let cosmos_amount: CosmosCoin = amount.clone().try_into()?; + let result = client!(state) + .send(&address, vec![cosmos_amount], memo) + .await?; + Ok(TauriTxResult::new( + result, + TransactionDetails { + from_address: client!(state).address().to_string(), + to_address: address.to_string(), + amount, + }, + )) } diff --git a/nym-wallet/src-tauri/src/operations/mod.rs b/nym-wallet/src-tauri/src/operations/mod.rs index 47f4788d6c..9bfd654e73 100644 --- a/nym-wallet/src-tauri/src/operations/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mod.rs @@ -1,5 +1,2 @@ -pub mod account; -pub mod admin; -pub mod bond; -pub mod delegate; -pub mod send; +pub mod mixnet; +pub mod vesting; diff --git a/nym-wallet/src-tauri/src/operations/vesting/bond.rs b/nym-wallet/src-tauri/src/operations/vesting/bond.rs new file mode 100644 index 0000000000..58a559b414 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/vesting/bond.rs @@ -0,0 +1,51 @@ +use crate::client; +use crate::coin::Coin; +use crate::error::BackendError; +use crate::state::State; +use crate::{Gateway, MixNode}; +use std::convert::TryInto; +use std::sync::Arc; +use tokio::sync::RwLock; +use validator_client::nymd::VestingSigningClient; + +#[tauri::command] +pub async fn vesting_bond_gateway( + gateway: Gateway, + pledge: Coin, + owner_signature: String, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state) + .vesting_bond_gateway(gateway, &owner_signature, pledge.try_into()?) + .await?; + Ok(()) +} + +#[tauri::command] +pub async fn vesting_unbond_gateway( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state).vesting_unbond_gateway().await?; + Ok(()) +} + +#[tauri::command] +pub async fn vesting_unbond_mixnode( + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state).vesting_unbond_mixnode().await?; + Ok(()) +} + +#[tauri::command] +pub async fn vesting_bond_mixnode( + mixnode: MixNode, + owner_signature: String, + pledge: Coin, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state) + .vesting_bond_mixnode(mixnode, &owner_signature, pledge.try_into()?) + .await?; + Ok(()) +} diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs new file mode 100644 index 0000000000..ed7bb14135 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -0,0 +1,42 @@ +use crate::client; +use crate::coin::Coin; +use crate::error::BackendError; +use crate::state::State; +use crate::utils::DelegationResult; +use cosmwasm_std::Coin as CosmWasmCoin; +use std::convert::TryInto; +use std::sync::Arc; +use tokio::sync::RwLock; +use validator_client::nymd::VestingSigningClient; + +#[tauri::command] +pub async fn vesting_delegate_to_mixnode( + identity: &str, + amount: Coin, + state: tauri::State<'_, Arc>>, +) -> Result { + let delegation: CosmWasmCoin = amount.try_into()?; + client!(state) + .vesting_delegate_to_mixnode(identity, &delegation) + .await?; + Ok(DelegationResult::new( + &client!(state).address().to_string(), + identity, + Some(delegation.into()), + )) +} + +#[tauri::command] +pub async fn vesting_undelegate_from_mixnode( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + client!(state) + .vesting_undelegate_from_mixnode(identity) + .await?; + Ok(DelegationResult::new( + &client!(state).address().to_string(), + identity, + None, + )) +} diff --git a/nym-wallet/src-tauri/src/operations/vesting/mod.rs b/nym-wallet/src-tauri/src/operations/vesting/mod.rs new file mode 100644 index 0000000000..361dd44ae6 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/vesting/mod.rs @@ -0,0 +1,3 @@ +pub mod bond; +pub mod delegate; +pub mod queries; diff --git a/nym-wallet/src-tauri/src/operations/vesting/queries.rs b/nym-wallet/src-tauri/src/operations/vesting/queries.rs new file mode 100644 index 0000000000..3512ce8a9d --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/vesting/queries.rs @@ -0,0 +1,146 @@ +use crate::client; +use crate::coin::Coin; +use crate::error::BackendError; +use crate::state::State; +use cosmwasm_std::Timestamp; +use std::sync::Arc; +use tokio::sync::RwLock; +use validator_client::nymd::VestingQueryClient; + +#[tauri::command] +pub async fn locked_coins( + address: &str, + block_time: Option, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + client!(state) + .locked_coins(address, block_time.map(Timestamp::from_seconds)) + .await? + .into(), + ) +} + +#[tauri::command] +pub async fn spendable_coins( + vesting_account_address: &str, + block_time: Option, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + client!(state) + .spendable_coins( + vesting_account_address, + 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>>, +) -> Result { + Ok( + 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>>, +) -> Result { + Ok( + 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>>, +) -> Result { + Ok( + 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>>, +) -> Result { + Ok( + 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>>, +) -> Result { + Ok( + 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>>, +) -> Result { + Ok( + 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>>, +) -> Result { + Ok( + client!(state) + .delegated_vesting( + vesting_account_address, + block_time.map(Timestamp::from_seconds), + ) + .await? + .into(), + ) +} diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index 40b21e9106..8603d0c592 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -1,4 +1,5 @@ use crate::config::Config; +use crate::error::BackendError; use validator_client::nymd::{NymdClient, SigningNymdClient}; #[derive(Default)] @@ -8,10 +9,11 @@ pub struct State { } impl State { - pub fn client(&self) -> Result<&NymdClient, String> { - self.signing_client.as_ref().ok_or_else(|| { - "Client has not been initialized yet, connect with mnemonic to initialize".to_string() - }) + pub fn client(&self) -> Result<&NymdClient, BackendError> { + self + .signing_client + .as_ref() + .ok_or(BackendError::ClientNotInitialized) } pub fn config(&self) -> Config { @@ -22,3 +24,10 @@ impl State { self.signing_client = Some(signing_client) } } + +#[macro_export] +macro_rules! client { + ($state:ident) => { + $state.read().await.client()? + }; +} diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index e06a0ea1ad..83b35ae9be 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -1,40 +1,46 @@ +use crate::client; use crate::coin::{Coin, Denom}; -use crate::format_err; +use crate::error::BackendError; use crate::state::State; use crate::Operation; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; #[tauri::command] -pub fn major_to_minor(amount: &str) -> Result { +pub fn major_to_minor(amount: &str) -> Coin { let coin = Coin::new(amount, &Denom::Major); - Ok(coin.to_minor()) + coin.to_minor() } #[tauri::command] -pub fn minor_to_major(amount: &str) -> Result { +pub fn minor_to_major(amount: &str) -> Coin { let coin = Coin::new(amount, &Denom::Minor); - Ok(coin.to_major()) + coin.to_major() } #[tauri::command] -pub async fn owns_mixnode(state: tauri::State<'_, Arc>>) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.owns_mixnode(client.address()).await { - Ok(o) => Ok(o.is_some()), - Err(e) => Err(format_err!(e)), - } +pub async fn owns_mixnode( + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + client!(state) + .owns_mixnode(client!(state).address()) + .await? + .is_some(), + ) } #[tauri::command] -pub async fn owns_gateway(state: tauri::State<'_, Arc>>) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - match client.owns_gateway(client.address()).await { - Ok(o) => Ok(o.is_some()), - Err(e) => Err(format_err!(e)), - } +pub async fn owns_gateway( + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + client!(state) + .owns_gateway(client!(state).address()) + .await? + .is_some(), + ) } // NOTE: this uses OUTDATED defaults that might have no resemblance with the reality @@ -43,14 +49,29 @@ pub async fn owns_gateway(state: tauri::State<'_, Arc>>) -> Result pub async fn get_approximate_fee( operation: Operation, state: tauri::State<'_, Arc>>, -) -> Result { - let r_state = state.read().await; - let client = r_state.client()?; - let approximate_fee = operation.default_fee(client.gas_price()); +) -> Result { + let approximate_fee = operation.default_fee(client!(state).gas_price()); let mut coin = Coin::new("0", &Denom::Major); for f in approximate_fee.amount { coin = coin + f.into(); } - Ok(coin) } + +#[cfg_attr(test, derive(ts_rs::TS))] +#[derive(Serialize, Deserialize)] +pub struct DelegationResult { + 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, + } + } +}