From eca77d684bf04721b5604cc67c5a5b898cbf30e1 Mon Sep 17 00:00:00 2001 From: Pierre Dommerc Date: Mon, 6 Mar 2023 18:26:07 +0100 Subject: [PATCH] feat(wallet): send - add user fees settings and memo field (#3146) * feat(wallet): send - add user fees settings, memo field * fix(wallet): send, custom fees validation --- .../validator-client/src/nyxd/coin.rs | 105 +++++++++++++++++- .../validator-client/src/nyxd/fee/mod.rs | 8 +- nym-wallet/src-tauri/src/main.rs | 1 + .../src/operations/simulate/cosmos.rs | 10 ++ nym-wallet/src-tauri/src/state.rs | 11 ++ .../src/components/Modals/ModalListItem.tsx | 7 +- .../components/Send/SendDetails.stories.tsx | 3 + .../src/components/Send/SendDetailsModal.tsx | 16 +++ .../src/components/Send/SendInputModal.tsx | 82 +++++++++++++- nym-wallet/src/components/Send/SendModal.tsx | 46 +++++++- nym-wallet/src/hooks/useGetFee.ts | 16 ++- nym-wallet/src/requests/simulate.ts | 3 + 12 files changed, 293 insertions(+), 15 deletions(-) diff --git a/common/client-libs/validator-client/src/nyxd/coin.rs b/common/client-libs/validator-client/src/nyxd/coin.rs index 8f1afe2a30..3ac51997b8 100644 --- a/common/client-libs/validator-client/src/nyxd/coin.rs +++ b/common/client-libs/validator-client/src/nyxd/coin.rs @@ -1,10 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; -use std::fmt; +use crate::nyxd::{Gas, GasPrice}; pub use cosmrs::Coin as CosmosCoin; pub use cosmwasm_std::Coin as CosmWasmCoin; +use cosmwasm_std::{Fraction, Uint128}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::ops::Div; #[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)] pub struct MismatchedDenoms; @@ -19,6 +22,40 @@ pub struct Coin { pub denom: String, } +impl Div for Coin { + type Output = Gas; + + fn div(self, rhs: GasPrice) -> Self::Output { + &self / rhs + } +} + +impl<'a> Div for &'a Coin { + type Output = Gas; + + fn div(self, rhs: GasPrice) -> Self::Output { + if self.denom != rhs.denom { + panic!( + "attempted to use two different denoms for gas calculation ({} and {})", + self.denom, rhs.denom + ); + } + + // tsk, tsk. somebody tried to divide by zero here! + let Some(gas_price_inv) = rhs.amount.inv() else { + panic!("attempted to divide by zero!") + }; + + let implicit_gas_limit = gas_price_inv * Uint128::new(self.amount); + if implicit_gas_limit.u128() >= u64::MAX as u128 { + u64::MAX + } else { + implicit_gas_limit.u128() as u64 + } + .into() + } +} + impl Coin { pub fn new>(amount: u128, denom: S) -> Self { Coin { @@ -128,3 +165,67 @@ impl CoinConverter for CosmWasmCoin { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn division_by_zero_gas_price() { + let gas_price: GasPrice = "0unym".parse().unwrap(); + let amount = Coin::new(123, "unym"); + let _res = amount / gas_price; + } + + #[test] + #[should_panic] + fn division_by_gas_price_of_different_denom() { + let gas_price: GasPrice = "0.025unyx".parse().unwrap(); + let amount = Coin::new(123, "unym"); + let _res = amount / gas_price; + } + + #[test] + fn gas_price_division() { + let amount = Coin::new(3938, "unym"); + let gas_price = "0.025unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(157520, res.value()); + + let amount = Coin::new(1234567890, "unym"); + let gas_price = "0.025unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(49382715600, res.value()); + + let amount = Coin::new(1, "unym"); + let gas_price = "0.025unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(40, res.value()); + + let amount = Coin::new(150_000_000, "unym"); + let gas_price = "0.001234unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(121555915721, res.value()); + + let amount = Coin::new(150_000_000, "unym"); + let gas_price = "1unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(150_000_000, res.value()); + + let amount = Coin::new(150_000_000, "unym"); + let gas_price = "1234.56unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(121500, res.value()); + } + + #[test] + fn gas_price_division_identity() { + let amount = Coin::new(1234567890, "unym"); + let gas_price: GasPrice = "0.025unym".parse().unwrap(); + let res1 = (&amount) / gas_price.clone(); + let res2 = &gas_price * res1; + + assert_eq!(amount, Coin::from(res2)); + } +} diff --git a/common/client-libs/validator-client/src/nyxd/fee/mod.rs b/common/client-libs/validator-client/src/nyxd/fee/mod.rs index b887c08128..e1ee4b4d5c 100644 --- a/common/client-libs/validator-client/src/nyxd/fee/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/mod.rs @@ -1,8 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nyxd::Coin; use crate::nyxd::Gas; +use crate::nyxd::{Coin, GasPrice}; use cosmrs::{tx, AccountId}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; @@ -64,6 +64,12 @@ impl Display for Fee { } impl Fee { + pub fn manual_with_gas_price(fee: Coin, gas_price: GasPrice) -> Self { + let gas_limit = &fee / gas_price; + + Fee::Manual(tx::Fee::from_amount_and_gas(fee.into(), gas_limit)) + } + pub fn new_payer_granter_auto( gas_adjustment: Option, payer: Option, diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 7c918624f1..70f43c3c96 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -143,6 +143,7 @@ fn main() { vesting::queries::vesting_start_time, simulate::admin::simulate_update_contract_settings, simulate::cosmos::simulate_send, + simulate::cosmos::get_custom_fees, simulate::mixnet::simulate_bond_gateway, simulate::mixnet::simulate_unbond_gateway, simulate::mixnet::simulate_bond_mixnode, diff --git a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs index 3679a1d863..a83aaf618a 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs @@ -33,3 +33,13 @@ pub async fn simulate_send( let result = client.nyxd.simulate(vec![msg]).await?; guard.create_detailed_fee(result) } + +#[tauri::command] +pub async fn get_custom_fees( + fees_amount: DecCoin, + state: tauri::State<'_, WalletState>, +) -> Result { + let guard = state.read().await; + let fee = guard.attempt_convert_to_fixed_fee(fees_amount.clone())?; + Ok(FeeDetails::new(Some(fees_amount), fee)) +} diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index ef7eaa1cb4..6e9474d3a4 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -88,6 +88,17 @@ pub(crate) struct WalletAccountIds { } impl WalletStateInner { + pub fn attempt_convert_to_fixed_fee(&self, coin: DecCoin) -> Result { + // first we have to convert the coin to its base denomination + let base_coin = self.attempt_convert_to_base_coin(coin)?; + + // then we get the gas price for the current network + let current_client = self.current_client()?; + let gas_price = current_client.nyxd.gas_price(); + + Ok(Fee::manual_with_gas_price(base_coin, gas_price.clone())) + } + // note that `Coin` is ALWAYS the base coin pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result { let registered_coins = self diff --git a/nym-wallet/src/components/Modals/ModalListItem.tsx b/nym-wallet/src/components/Modals/ModalListItem.tsx index 7e5194a21c..916603e011 100644 --- a/nym-wallet/src/components/Modals/ModalListItem.tsx +++ b/nym-wallet/src/components/Modals/ModalListItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box, Stack, Typography, TypographyProps } from '@mui/material'; +import { Box, Stack, SxProps, Typography, TypographyProps } from '@mui/material'; import { ModalDivider } from './ModalDivider'; export const ModalListItem: FCWithChildren<{ @@ -9,14 +9,15 @@ export const ModalListItem: FCWithChildren<{ fontWeight?: TypographyProps['fontWeight']; light?: boolean; value?: React.ReactNode; -}> = ({ label, value, hidden, fontWeight, divider }) => ( + sxValue?: SxProps; +}> = ({ label, value, hidden, fontWeight, divider, sxValue }) => (