Feature/nymd client fee handling (#730)

* Calculating gas fees

* Ability to set custom fees

* Added extra test

* Removed commented code

* Removed needless borrow
This commit is contained in:
Jędrzej Stuczyński
2021-08-11 12:01:53 +01:00
committed by GitHub
parent 4ac95ad8cf
commit fdd34863ba
5 changed files with 198 additions and 10 deletions
@@ -137,6 +137,10 @@ pub enum ValidatorClientError {
code: u32,
raw_log: String,
},
#[cfg(feature = "nymd-client")]
#[error("The provided gas price is malformed")]
MalformedGasPrice,
}
#[cfg(feature = "nymd-client")]
@@ -0,0 +1,88 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::GasPrice;
use cosmos_sdk::tx::{Fee, Gas};
use cosmos_sdk::Coin;
use cosmwasm_std::Uint128;
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum Operation {
Upload,
Init,
Migrate,
ChangeAdmin,
Send,
BondMixnode,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
let limit_uint128 = Uint128::from(gas_limit.value());
let amount = gas_price.amount * limit_uint128;
assert!(amount.u128() <= u64::MAX as u128);
Coin {
denom: gas_price.denom.clone(),
amount: (amount.u128() as u64).into(),
}
}
impl Operation {
pub(crate) fn default_gas_limit(&self) -> Gas {
match self {
Operation::Upload => 2_500_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(),
}
}
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
// we need to know 2 of the following 3 parameters (the third one is being implicit) in order to construct Fee:
// (source: https://docs.cosmos.network/v0.42/basics/gas-fees.html)
// - gas price
// - gas limit
// - fees
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
let fee = calculate_fee(gas_price, gas_limit);
Fee::from_amount_and_gas(fee, gas_limit)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calculating_fee() {
let expected = Coin {
denom: "upunk".parse().unwrap(),
amount: 1000u64.into(),
};
let gas_price = "1upunk".parse().unwrap();
let gas_limit = 1000u64.into();
assert_eq!(expected, calculate_fee(&gas_price, gas_limit));
let expected = Coin {
denom: "upunk".parse().unwrap(),
amount: 50u64.into(),
};
let gas_price = "0.05upunk".parse().unwrap();
let gas_limit = 1000u64.into();
assert_eq!(expected, calculate_fee(&gas_price, gas_limit));
let expected = Coin {
denom: "upunk".parse().unwrap(),
amount: 100000u64.into(),
};
let gas_price = "100upunk".parse().unwrap();
let gas_limit = 1000u64.into();
assert_eq!(expected, calculate_fee(&gas_price, gas_limit))
}
}
@@ -0,0 +1,76 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::ValidatorClientError;
use config::defaults;
use cosmos_sdk::Denom;
use cosmwasm_std::Decimal;
use std::str::FromStr;
/// A gas price, i.e. the price of a single unit of gas. This is typically a fraction of
/// 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
pub amount: Decimal,
pub denom: Denom,
}
impl FromStr for GasPrice {
type Err = ValidatorClientError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let possible_amount = s
.chars()
.take_while(|c| c.is_ascii_digit() || c == &'.')
.collect::<String>();
let amount_len = possible_amount.len();
let amount = possible_amount
.parse()
.map_err(|_| ValidatorClientError::MalformedGasPrice)?;
let possible_denom = s.chars().skip(amount_len).collect::<String>();
let denom = possible_denom
.parse()
.map_err(|_| ValidatorClientError::MalformedGasPrice)?;
Ok(GasPrice { amount, denom })
}
}
impl Default for GasPrice {
fn default() -> Self {
format!("{}{}", defaults::GAS_PRICE_AMOUNT, defaults::DENOM)
.parse()
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gas_price_parsing() {
assert_eq!(
GasPrice {
amount: "0.025".parse().unwrap(),
denom: "upunk".parse().unwrap()
},
"0.025upunk".parse().unwrap()
);
assert_eq!(
GasPrice {
amount: "123".parse().unwrap(),
denom: "upunk".parse().unwrap()
},
"123upunk".parse().unwrap()
);
assert!(".25upunk".parse::<GasPrice>().is_err());
assert!("0.025 upunk".parse::<GasPrice>().is_err());
assert!("0.025UPUNK".parse::<GasPrice>().is_err());
}
}
@@ -6,23 +6,30 @@ use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::signing_client;
use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::fee_helpers::Operation;
pub use crate::nymd::gas_price::GasPrice;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::ValidatorClientError;
use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use cosmos_sdk::tx::Fee;
use cosmos_sdk::tx::Gas;
use cosmos_sdk::Coin as CosmosCoin;
use cosmos_sdk::{AccountId, Denom};
use cosmwasm_std::Coin;
use mixnet_contract::LayerDistribution;
use std::collections::HashMap;
use std::convert::TryInto;
pub mod cosmwasm_client;
pub(crate) mod fee_helpers;
pub mod gas_price;
pub mod wallet;
pub struct NymdClient<C> {
client: C,
contract_address: AccountId,
client_address: Option<Vec<AccountId>>,
gas_price: GasPrice,
custom_gas_limits: HashMap<Operation, Gas>,
}
impl NymdClient<HttpClient> {
@@ -37,6 +44,8 @@ impl NymdClient<HttpClient> {
client: HttpClient::new(endpoint)?,
contract_address,
client_address: None,
gas_price: Default::default(),
custom_gas_limits: Default::default(),
})
}
}
@@ -61,6 +70,8 @@ impl NymdClient<signing_client::Client> {
client: signing_client::Client::connect_with_signer(endpoint, signer)?,
contract_address,
client_address: Some(client_address),
gas_price: Default::default(),
custom_gas_limits: Default::default(),
})
}
@@ -83,11 +94,21 @@ impl NymdClient<signing_client::Client> {
client: signing_client::Client::connect_with_signer(endpoint, wallet)?,
contract_address,
client_address: Some(client_address),
gas_price: Default::default(),
custom_gas_limits: Default::default(),
})
}
}
impl<C> NymdClient<C> {
pub fn set_gas_price(&mut self, gas_price: GasPrice) {
self.gas_price = gas_price
}
pub fn set_custom_gas_limit(&mut self, operation: Operation, limit: Gas) {
self.custom_gas_limits.insert(operation, limit);
}
pub fn address(&self) -> &AccountId
where
C: SigningCosmWasmClient,
@@ -97,7 +118,7 @@ impl<C> NymdClient<C> {
}
// now the question is as follows: will denom always be in the format of `u{prefix}`?
fn denom(&self) -> Denom {
pub fn denom(&self) -> Denom {
format!("u{}", self.contract_address.prefix())
.parse()
.unwrap()
@@ -114,16 +135,12 @@ impl<C> NymdClient<C> {
where
C: SigningCosmWasmClient + Sync,
{
// TODO: will need to create some nice fee table, for now, for test sake, just use hardcoded value
let fee_amount = CosmosCoin {
amount: 100_0000u64.into(),
denom: self.denom(),
};
let gas = 100_0000;
let fee = Fee::from_amount_and_gas(fee_amount, gas);
let fee = Operation::BondMixnode.determine_fee(
&self.gas_price,
self.custom_gas_limits.get(&Operation::BondMixnode).cloned(),
);
let req = ExecuteMsg::BondMixnode { mix_node: mixnode };
self.client
.execute(
self.address(),
+3
View File
@@ -11,6 +11,9 @@ pub const NETWORK_MONITOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfd
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
pub const BECH32_PREFIX: &str = "punk";
pub const DENOM: &str = "upunk";
// as set by validators in their configs
// (note that the 'amount' postfix is relevant here as the full gas price also includes denom)
pub const GAS_PRICE_AMOUNT: f64 = 0.025;
pub const DEFAULT_MIX_LISTENING_PORT: u16 = 1789;