Broken, but compilable first stage of post-merging

This commit is contained in:
Jędrzej Stuczyński
2022-06-01 09:57:40 +01:00
67 changed files with 5931 additions and 5207 deletions
+13
View File
@@ -9,17 +9,27 @@
- 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]).
- 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])
- 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
@@ -29,6 +39,9 @@
[#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
[#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)
+6 -13
View File
@@ -1,19 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// 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<SigningNymdClient>,
@@ -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)
@@ -0,0 +1,130 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// 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<S: Into<String>>(amount: u128, denom: S) -> Self {
Coin {
amount,
denom: denom.into(),
}
}
pub fn try_add(&self, other: &Self) -> Result<Self, MismatchedDenoms> {
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<Coin> 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<CosmosCoin> 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<Coin> for CosmWasmCoin {
fn from(coin: Coin) -> Self {
CosmWasmCoin::new(coin.amount, coin.denom)
}
}
impl From<CosmWasmCoin> 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(),
}
}
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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::<Result<_, _>>()
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
}
// this is annoyingly and inconsistently returning `Vec<CosmWasmCoin>` rather than
// Vec<Coin>, 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<Vec<CosmWasmCoin>, NymdError> {
async fn get_total_supply(&self) -> Result<Vec<Coin>, 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::<Result<_, _>>()
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
}
@@ -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()))?;
@@ -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<ProtoModuleAccount> for ModuleAccount {
#[derive(Debug)]
pub struct BaseVestingAccount {
pub base_account: Option<BaseAccount>,
pub original_vesting: Vec<Coin>,
pub delegated_free: Vec<Coin>,
pub delegated_vesting: Vec<Coin>,
pub original_vesting: Vec<CosmosCoin>,
pub delegated_free: Vec<CosmosCoin>,
pub delegated_vesting: Vec<CosmosCoin>,
pub end_time: i64,
}
@@ -184,7 +184,7 @@ impl TryFrom<ProtoDelayedVestingAccount> for DelayedVestingAccount {
#[derive(Debug)]
pub struct Period {
pub length: i64,
pub amount: Vec<Coin>,
pub amount: Vec<CosmosCoin>,
}
impl TryFrom<ProtoPeriod> 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<Coin>,
pub funds: Vec<CosmosCoin>,
/// 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<AccountId>,
}
impl InstantiateOptions {
pub fn new<T: Into<CosmosCoin>>(funds: Vec<T>, admin: Option<AccountId>) -> Self {
InstantiateOptions {
funds: funds.into_iter().map(Into::into).collect(),
admin,
}
}
}
#[derive(Debug)]
pub struct InstantiateResult {
/// The address of the newly instantiated contract
@@ -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<Gas> for &'a GasPrice {
@@ -44,7 +45,10 @@ impl<'a> Mul<Gas> 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::<String>();
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::<GasPrice>().is_err());
assert!("0.025 upunk".parse::<GasPrice>().is_err());
assert_eq!(
"0.025upunk".parse::<GasPrice>().unwrap(),
"0.025 upunk".parse().unwrap()
);
let gas: GasPrice = "0.025 upunk ".parse().unwrap();
assert_eq!("upunk", gas.denom);
}
#[test]
@@ -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<C> NymdClient<C> {
&self,
contract_address: &AccountId,
msg: &M,
funds: Vec<CosmosCoin>,
funds: Vec<Coin>,
) -> Result<cosmwasm::MsgExecuteContract, NymdError>
where
C: SigningCosmWasmClient,
@@ -185,7 +188,7 @@ impl<C> NymdClient<C> {
sender: self.address().clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(msg)?,
funds,
funds: funds.into_iter().map(Into::into).collect(),
})
}
@@ -275,7 +278,7 @@ impl<C> NymdClient<C> {
&self,
address: &AccountId,
denom: Denom,
) -> Result<Option<CosmosCoin>, NymdError>
) -> Result<Option<Coin>, NymdError>
where
C: CosmWasmClient + Sync,
{
@@ -650,7 +653,7 @@ impl<C> NymdClient<C> {
pub async fn send(
&self,
recipient: &AccountId,
amount: Vec<CosmosCoin>,
amount: Vec<Coin>,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
@@ -666,7 +669,7 @@ impl<C> NymdClient<C> {
/// Send funds from one address to multiple others
pub async fn send_multiple(
&self,
msgs: Vec<(AccountId, Vec<CosmosCoin>)>,
msgs: Vec<(AccountId, Vec<Coin>)>,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
@@ -685,7 +688,7 @@ impl<C> NymdClient<C> {
msg: &M,
fee: Fee,
memo: impl Into<String> + Send + 'static,
funds: Vec<CosmosCoin>,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
@@ -705,7 +708,7 @@ impl<C> NymdClient<C> {
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
I: IntoIterator<Item = (M, Vec<CosmosCoin>)> + Send,
I: IntoIterator<Item = (M, Vec<Coin>)> + Send,
M: Serialize,
{
self.client
@@ -796,6 +799,89 @@ impl<C> NymdClient<C> {
.await
}
pub async fn compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
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<Fee>) -> Result<ExecuteResult, NymdError>
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<Fee>,
) -> Result<ExecuteResult, NymdError>
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<Fee>,
) -> Result<ExecuteResult, NymdError>
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,
@@ -820,7 +906,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding mixnode from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -851,7 +937,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding mixnode on behalf from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -867,7 +953,7 @@ impl<C> NymdClient<C> {
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let reqs: Vec<(ExecuteMsg, Vec<CosmosCoin>)> = mixnode_bonds_with_sigs
let reqs: Vec<(ExecuteMsg, Vec<Coin>)> = mixnode_bonds_with_sigs
.into_iter()
.map(|(bond, owner_signature)| {
(
@@ -876,7 +962,7 @@ impl<C> NymdClient<C> {
owner: bond.owner.to_string(),
owner_signature,
},
vec![cosmwasm_coin_to_cosmos_coin(bond.pledge_amount)],
vec![bond.pledge_amount.into()],
)
})
.collect();
@@ -907,7 +993,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding mixnode from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -931,7 +1017,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding mixnode on behalf from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -957,7 +1043,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Updating mixnode configuration from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -966,7 +1052,7 @@ impl<C> NymdClient<C> {
pub async fn delegate_to_mixnode(
&self,
mix_identity: &str,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
@@ -984,7 +1070,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Delegating to mixnode from rust!",
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
vec![amount],
)
.await
}
@@ -995,7 +1081,7 @@ impl<C> NymdClient<C> {
&self,
mix_identity: &str,
delegate: &str,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
@@ -1014,7 +1100,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Delegating to mixnode on behalf from rust!",
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
vec![amount],
)
.await
}
@@ -1030,7 +1116,7 @@ impl<C> NymdClient<C> {
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let reqs: Vec<(ExecuteMsg, Vec<CosmosCoin>)> = mixnode_delegations
let reqs: Vec<(ExecuteMsg, Vec<Coin>)> = mixnode_delegations
.into_iter()
.map(|delegation| {
(
@@ -1038,7 +1124,7 @@ impl<C> NymdClient<C> {
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();
@@ -1075,7 +1161,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Removing mixnode delegation from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1103,7 +1189,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Removing mixnode delegation on behalf from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1132,7 +1218,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding gateway from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -1163,7 +1249,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding gateway on behalf from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -1179,7 +1265,7 @@ impl<C> NymdClient<C> {
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let reqs: Vec<(ExecuteMsg, Vec<CosmosCoin>)> = gateway_bonds_with_sigs
let reqs: Vec<(ExecuteMsg, Vec<Coin>)> = gateway_bonds_with_sigs
.into_iter()
.map(|(bond, owner_signature)| {
(
@@ -1188,7 +1274,7 @@ impl<C> NymdClient<C> {
owner: bond.owner.to_string(),
owner_signature,
},
vec![cosmwasm_coin_to_cosmos_coin(bond.pledge_amount)],
vec![bond.pledge_amount.into()],
)
})
.collect();
@@ -1219,7 +1305,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding gateway from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1244,7 +1330,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding gateway on behalf from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1267,7 +1353,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Updating contract state from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1286,7 +1372,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Advance current epoch",
Vec::new(),
vec![],
)
.await
}
@@ -1305,7 +1391,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Reconciling delegation events",
Vec::new(),
vec![],
)
.await
}
@@ -1324,7 +1410,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Snapshotting mixnodes",
Vec::new(),
vec![],
)
.await
}
@@ -1351,24 +1437,8 @@ impl<C> NymdClient<C> {
&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(),
}
}
@@ -1,11 +1,12 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
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<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
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<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
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<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
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<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
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)
}
/// Returns the total amount of delegated tokens that have vested
@@ -188,8 +194,9 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
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<Account, NymdError> {
@@ -4,14 +4,35 @@
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};
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
profix_margin_percent: u8,
@@ -74,7 +95,7 @@ pub trait VestingSigningClient {
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -149,7 +170,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
let req = VestingExecuteMsg::BondGateway {
gateway,
owner_signature: owner_signature.to_string(),
amount: pledge,
amount: pledge.into(),
};
self.client
.execute(
@@ -187,7 +208,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> 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(
@@ -212,7 +233,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
amount: pledge,
amount: pledge.into(),
};
self.client
.execute(
@@ -250,7 +271,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> 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(
@@ -269,7 +290,9 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
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(),
@@ -292,7 +315,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount,
amount: amount.into(),
};
self.client
.execute(
@@ -308,13 +331,13 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
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(
@@ -370,7 +393,81 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
&req,
fee,
"VestingContract::CreatePeriodicVestingAccount",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
vec![amount],
)
.await
}
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
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<Fee>,
) -> Result<ExecuteResult, NymdError> {
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<Fee>,
) -> Result<ExecuteResult, NymdError> {
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<Fee>,
) -> Result<ExecuteResult, NymdError> {
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
}
@@ -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<Addr>,
@@ -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<Addr>,
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<Addr>,
@@ -310,6 +310,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,
@@ -402,63 +410,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<RewardEstimate, MixnetContractError> {
let total_node_reward = self
.reward(params)
.reward()
.checked_to_num::<u128>()
.unwrap_or_default();
let node_profit = self
.node_profit(params)
.checked_to_num::<u128>()
.unwrap_or_default();
let operator_cost = params
.node
.operator_cost()
.checked_to_num::<u128>()
.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 {
@@ -466,11 +491,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()
@@ -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)]
@@ -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 {
@@ -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)
}
@@ -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,
},
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -35,7 +35,7 @@ impl TryFrom<MixnetContractDelegation> for Delegation {
proxy,
} = value;
let amount: MajorCurrencyAmount = amount.try_into()?;
let amount: MajorCurrencyAmount = amount.into();
Ok(Delegation {
owner: owner.into_string(),
@@ -112,7 +112,7 @@ impl TryFrom<MixnetContractDelegation> for DelegationResult {
type Error = TypesError;
fn try_from(delegation: MixnetContractDelegation) -> Result<Self, Self::Error> {
let amount: MajorCurrencyAmount = delegation.amount.clone().try_into()?;
let amount: MajorCurrencyAmount = delegation.amount.clone().into();
Ok(DelegationResult {
source_address: delegation.owner().to_string(),
target_address: delegation.node_identity(),
@@ -152,7 +152,7 @@ impl TryFrom<ContractDelegationEvent> for DelegationEvent {
fn try_from(event: ContractDelegationEvent) -> Result<Self, Self::Error> {
match event {
ContractDelegationEvent::Delegate(delegation) => {
let amount: MajorCurrencyAmount = delegation.amount.try_into()?;
let amount: MajorCurrencyAmount = delegation.amount.into();
Ok(DelegationEvent {
kind: DelegationEventKind::Delegate,
block_height: delegation.block_height,
+29 -25
View File
@@ -26,35 +26,39 @@ pub struct Gas {
impl Gas {
pub fn from_cosmrs_gas(value: CosmrsGas, denom_minor: &str) -> Result<Gas, TypesError> {
// TODO: use simulator struct to do conversion to fee
let value_u128 = Uint128::from(value.value());
let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
Ok(Gas {
gas_units: value.value(),
amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
})
todo!()
// // TODO: use simulator struct to do conversion to fee
// let value_u128 = Uint128::from(value.value());
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
// Ok(Gas {
// gas_units: value.value(),
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
// })
}
pub fn from_u64(value: u64, denom_minor: &str) -> Result<Gas, TypesError> {
// TODO: use simulator struct to do conversion to fee
let value_u128 = Uint128::from(value);
let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
Ok(Gas {
gas_units: value,
amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
})
todo!()
// // TODO: use simulator struct to do conversion to fee
// let value_u128 = Uint128::from(value);
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
// Ok(Gas {
// gas_units: value,
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
// })
}
pub fn from_gas_price(value: ValidatorClientGasPrice) -> Result<Gas, TypesError> {
// TODO: use simulator struct to do conversion to fee
let gas_units_str = (value.amount / Uint128::from_str("0.0025")?).to_string();
let decimal_seperator_pos = gas_units_str.find('.').unwrap_or(gas_units_str.len());
let gas_units = gas_units_str[..decimal_seperator_pos]
.parse()
.unwrap_or(0_u64);
let ValidatorClientGasPrice { amount, denom } = value;
Ok(Gas {
gas_units,
amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom.as_ref())?,
})
todo!()
// // TODO: use simulator struct to do conversion to fee
// let gas_units_str = (value.amount / Uint128::from_str("0.0025")?).to_string();
// let decimal_seperator_pos = gas_units_str.find('.').unwrap_or(gas_units_str.len());
// let gas_units = gas_units_str[..decimal_seperator_pos]
// .parse()
// .unwrap_or(0_u64);
// let ValidatorClientGasPrice { amount, denom } = value;
// Ok(Gas {
// gas_units,
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom.as_ref())?,
// })
}
}
+1 -1
View File
@@ -87,7 +87,7 @@ impl TryFrom<MixnetContractGatewayBond> for GatewayBond {
proxy,
} = value;
let pledge_amount: MajorCurrencyAmount = pledge_amount.try_into()?;
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
Ok(GatewayBond {
pledge_amount,
+8 -7
View File
@@ -1,7 +1,8 @@
use crate::currency::MajorCurrencyAmount;
use crate::error::TypesError;
use mixnet_contract_common::{
MixNode as MixnetContractMixNode, MixNodeBond as MixnetContractMixNodeBond,
Coin as CosmWasmCoin, MixNode as MixnetContractMixNode,
MixNodeBond as MixnetContractMixNodeBond,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -102,12 +103,12 @@ impl TryFrom<MixnetContractMixNodeBond> for MixNodeBond {
));
}
let pledge_amount: MajorCurrencyAmount = pledge_amount.try_into()?;
let total_delegation: MajorCurrencyAmount = total_delegation.try_into()?;
let accumulated_rewards: Option<MajorCurrencyAmount> = accumulated_rewards.and_then(|r| {
MajorCurrencyAmount::from_minor_uint128_and_denom(r, &pledge_amount.denom.to_string())
.ok()
});
let denom = total_delegation.denom.clone();
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
let total_delegation: MajorCurrencyAmount = total_delegation.into();
let accumulated_rewards: Option<MajorCurrencyAmount> =
accumulated_rewards.map(|r| CosmWasmCoin::new(r.u128(), denom).into());
Ok(MixNodeBond {
pledge_amount,
+12 -10
View File
@@ -20,8 +20,8 @@ pub struct SendTxResult {
pub details: TransactionDetails,
pub gas_used: u64,
pub gas_wanted: u64,
pub fee: MajorCurrencyAmount,
pub tx_hash: String,
// pub fee: MajorCurrencyAmount,
}
impl SendTxResult {
@@ -37,10 +37,11 @@ impl SendTxResult {
gas_used: t.tx_result.gas_used.value(),
gas_wanted: t.tx_result.gas_wanted.value(),
tx_hash: t.hash.to_string(),
fee: MajorCurrencyAmount::from_decimal_and_denom(
Decimal::new(Uint128::from(t.tx_result.gas_used.value())),
denom_minor.to_string(),
)?,
// that is completely wrong: fee is what you told the validator to use beforehandn
// fee: MajorCurrencyAmount::from_decimal_and_denom(
// Decimal::new(Uint128::from(t.tx_result.gas_used.value())),
// denom_minor.to_string(),
// )?,
})
}
}
@@ -100,7 +101,7 @@ pub struct RpcTransactionResponse {
pub block_height: u64,
pub transaction_hash: String,
pub gas_info: GasInfo,
pub fee: MajorCurrencyAmount,
// pub fee: MajorCurrencyAmount,
}
impl RpcTransactionResponse {
@@ -118,10 +119,11 @@ impl RpcTransactionResponse {
transaction_hash: value.hash.to_string(),
tx_result_json: ::serde_json::to_string_pretty(&value.tx_result)?,
block_height: value.height.value(),
fee: MajorCurrencyAmount::from_decimal_and_denom(
Decimal::new(Uint128::from(value.tx_result.gas_used.value())),
denom_minor.to_string(),
)?,
// wrong
// fee: MajorCurrencyAmount::from_decimal_and_denom(
// Decimal::new(Uint128::from(value.tx_result.gas_used.value())),
// denom_minor.to_string(),
// )?,
})
}
}
+3 -3
View File
@@ -21,7 +21,7 @@ impl TryFrom<VestingPledgeData> for PledgeData {
type Error = TypesError;
fn try_from(data: VestingPledgeData) -> Result<Self, Self::Error> {
let amount: MajorCurrencyAmount = data.amount().try_into()?;
let amount: MajorCurrencyAmount = data.amount().into();
Ok(Self {
amount,
block_time: data.block_time().seconds(),
@@ -51,7 +51,7 @@ impl TryFrom<VestingOriginalVestingResponse> for OriginalVestingResponse {
type Error = TypesError;
fn try_from(data: VestingOriginalVestingResponse) -> Result<Self, Self::Error> {
let amount = data.amount().try_into()?;
let amount = data.amount().into();
Ok(Self {
amount,
number_of_periods: data.number_of_periods(),
@@ -82,7 +82,7 @@ impl TryFrom<VestingAccount> for VestingAccountInfo {
for period in account.periods() {
periods.push(period.into());
}
let amount: MajorCurrencyAmount = account.coin().try_into()?;
let amount: MajorCurrencyAmount = account.coin().into();
Ok(Self {
owner_address: account.owner_address().to_string(),
staking_address: account.staking_address().map(|a| a.to_string()),
+49
View File
@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "aes"
version = "0.7.5"
@@ -1040,6 +1046,7 @@ dependencies = [
"serde_repr",
"thiserror",
"time 0.3.6",
"ts-rs",
]
[[package]]
@@ -1573,6 +1580,15 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.30"
@@ -1645,6 +1661,29 @@ dependencies = [
"serde",
]
[[package]]
name = "ts-rs"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc59f479df54269b400dd95bc3b7e81623b3e4b9c70c8ca7125ab8341eafa64e"
dependencies = [
"thiserror",
"ts-rs-macros",
]
[[package]]
name = "ts-rs-macros"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff"
dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn",
"termcolor",
]
[[package]]
name = "typenum"
version = "1.15.0"
@@ -1755,6 +1794,7 @@ dependencies = [
"mixnet-contract-common",
"schemars",
"serde",
"ts-rs",
]
[[package]]
@@ -1839,6 +1879,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
+26
View File
@@ -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,
),
}
}
+2 -2
View File
@@ -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,
+198 -9
View File
@@ -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<Response, ContractError> {
_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<Response, ContractError> {
_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<Addr>,
) -> Result<Response, ContractError> {
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<Addr>,
) -> Result<Response, ContractError> {
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<Response, ContractError> {
_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<Response, ContractError> {
_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,
@@ -449,7 +633,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 +1565,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 +1579,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(&params);
@@ -1407,9 +1591,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(&params).int(), 203558u128);
assert_eq!(mix_1.node_profit(&params).int(), 183202u128);
assert_ne!(
mix_1_reward_result.reward(),
mix_1.node_profit(&params).int()
);
let mix1_operator_reward = mix_1.operator_reward(&params);
@@ -1417,9 +1606,9 @@ pub mod tests {
let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), &params);
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(),
+40 -1
View File
@@ -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<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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<Response, ContractError> {
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,
@@ -8,6 +8,8 @@ pub trait MixnodeBondingAccount {
storage: &dyn Storage,
) -> Result<Response, ContractError>;
fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
fn try_bond_mixnode(
&self,
mix_node: MixNode,
@@ -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<Response, ContractError>;
fn try_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
@@ -73,4 +73,5 @@ pub trait VestingAccount {
to_address: Option<Addr>,
storage: &mut dyn Storage,
) -> Result<(), ContractError>;
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>;
}
@@ -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<Response, ContractError> {
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,
@@ -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<Response, ContractError> {
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,
@@ -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<Timestamp>,
+1 -1
View File
@@ -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,
})
}
-13
View File
@@ -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
+1 -1
View File
@@ -1,3 +1,3 @@
fn main() {
tauri_build::build()
tauri_build::build()
}
+397 -400
View File
@@ -22,7 +22,7 @@ use crate::error::BackendError;
use crate::platform_constants::{CONFIG_DIR_NAME, CONFIG_FILENAME};
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;
@@ -30,448 +30,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<GlobalConfig>,
// Global configuration file
global: Option<GlobalConfig>,
// One configuration file per network
networks: HashMap<String, NetworkConfig>,
// One configuration file per network
networks: HashMap<String, NetworkConfig>,
}
#[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<u32>,
// TODO: there are no global settings (yet)
version: Option<u32>,
// TODO: there are no global settings (yet)
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct NetworkConfig {
version: Option<u32>,
version: Option<u32>,
// User selected urls
selected_nymd_url: Option<Url>,
selected_api_url: Option<Url>,
// User selected urls
selected_nymd_url: Option<Url>,
selected_api_url: Option<Url>,
// Additional user provided validators.
// It is an option for the purpuse of file serialization.
validator_urls: Option<Vec<ValidatorConfigEntry>>,
// Additional user provided validators.
// It is an option for the purpuse of file serialization.
validator_urls: Option<Vec<ValidatorConfigEntry>>,
}
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<Item = &ValidatorConfigEntry> {
self.validator_urls.iter().flat_map(|v| v.iter())
}
fn validators(&self) -> impl Iterator<Item = &ValidatorConfigEntry> {
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<WalletNetwork>) -> 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<WalletNetwork>) -> 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::<GlobalConfig>(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::<NetworkConfig>(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::<GlobalConfig>(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::<NetworkConfig>(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<Item = ValidatorConfigEntry> + '_ {
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<Item = ValidatorConfigEntry> + '_ {
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<Item = ValidatorConfigEntry> + '_ {
self
.networks
.get(&network.as_key())
.into_iter()
.flat_map(|c| c.validators().cloned())
}
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option<CosmosAccountId> {
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<CosmosAccountId> {
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<CosmosAccountId> {
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<Item = ValidatorConfigEntry> + '_ {
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<CosmosAccountId> {
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<Url> {
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<Url> {
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<CosmosAccountId> {
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<CosmosAccountId> {
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<Url> {
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<Url> {
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<T>(file: PathBuf) -> Result<T, io::Error>
where
T: DeserializeOwned,
T: DeserializeOwned,
{
fs::read_to_string(file).and_then(|contents| {
toml::from_str::<T>(&contents)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
})
fs::read_to_string(file).and_then(|contents| {
toml::from_str::<T>(&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<String>,
pub api_url: Option<Url>,
pub nymd_url: Url,
pub nymd_name: Option<String>,
pub api_url: Option<Url>,
}
impl TryFrom<ValidatorDetails> for ValidatorConfigEntry {
type Error = BackendError;
type Error = BackendError;
fn try_from(validator: ValidatorDetails) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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<network_config::Validator> for ValidatorConfigEntry {
type Error = BackendError;
type Error = BackendError;
fn try_from(validator: network_config::Validator) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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<Vec<ValidatorConfigEntry>>,
sandbox: Option<Vec<ValidatorConfigEntry>>,
qa: Option<Vec<ValidatorConfigEntry>>,
// 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<Vec<ValidatorConfigEntry>>,
sandbox: Option<Vec<ValidatorConfigEntry>>,
qa: Option<Vec<ValidatorConfigEntry>>,
}
impl OptionalValidators {
pub fn validators(&self, network: WalletNetwork) -> impl Iterator<Item = &ValidatorConfigEntry> {
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<Item = &ValidatorConfigEntry> {
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]]
@@ -486,17 +483,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/",
@@ -518,53 +515,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/",);
}
}
+106 -106
View File
@@ -7,117 +7,117 @@ use validator_client::{nymd::error::NymdError, ValidatorClientError};
#[derive(Error, Debug)]
pub enum BackendError {
#[error("{source}")]
TypesError {
#[from]
source: TypesError,
},
#[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("The provided network is not supported (yet)")]
NetworkNotSupported(config::defaults::all::Network),
#[error("Could not access the local data storage directory")]
UnknownStorageDirectory,
#[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("Unexpected mnemonic account for login")]
WalletUnexpectedMnemonicAccount,
// #[error("Unexpected multiple account entry for login")]
// WalletUnexpectedMultipleAccounts,
#[error("Failed to derive address from mnemonic")]
FailedToDeriveAddress,
#[error("{source}")]
TypesError {
#[from]
source: TypesError,
},
#[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("The provided network is not supported (yet)")]
NetworkNotSupported(config::defaults::all::Network),
#[error("Could not access the local data storage directory")]
UnknownStorageDirectory,
#[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("Unexpected mnemonic account for login")]
WalletUnexpectedMnemonicAccount,
// #[error("Unexpected multiple account entry for login")]
// WalletUnexpectedMultipleAccounts,
#[error("Failed to derive address from mnemonic")]
FailedToDeriveAddress,
}
impl Serialize for BackendError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl From<ValidatorClientError> 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(),
}
}
}
}
+122 -122
View File
@@ -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 mixnet_contract_common::{Gateway, MixNode};
@@ -27,130 +27,130 @@ use crate::operations::vesting;
use crate::state::State;
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_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_delegation_summary,
mixnet::delegate::get_all_pending_delegation_events,
mixnet::delegate::get_all_mix_delegations,
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::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::admin::simulate_update_contract_settings,
simulate::cosmos::simulate_send,
simulate::mixnet::simulate_bond_gateway,
simulate::mixnet::simulate_unbond_gateway,
simulate::mixnet::simulate_bond_mixnode,
simulate::mixnet::simulate_unbond_mixnode,
simulate::mixnet::simulate_update_mixnode,
simulate::mixnet::simulate_delegate_to_mixnode,
simulate::mixnet::simulate_undelegate_from_mixnode,
simulate::vesting::simulate_vesting_bond_gateway,
simulate::vesting::simulate_vesting_unbond_gateway,
simulate::vesting::simulate_vesting_bond_mixnode,
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_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_delegation_summary,
mixnet::delegate::get_all_pending_delegation_events,
mixnet::delegate::get_all_mix_delegations,
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::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::admin::simulate_update_contract_settings,
simulate::cosmos::simulate_send,
simulate::mixnet::simulate_bond_gateway,
simulate::mixnet::simulate_unbond_gateway,
simulate::mixnet::simulate_bond_mixnode,
simulate::mixnet::simulate_unbond_mixnode,
simulate::mixnet::simulate_update_mixnode,
simulate::mixnet::simulate_delegate_to_mixnode,
simulate::mixnet::simulate_undelegate_from_mixnode,
simulate::vesting::simulate_vesting_bond_gateway,
simulate::vesting::simulate_vesting_unbond_gateway,
simulate::vesting::simulate_vesting_bond_mixnode,
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);
}
if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() {
log_builder.filter_module("nym_wallet::operations", log::LevelFilter::Trace);
}
if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() {
log_builder.filter_module("nym_wallet::operations", log::LevelFilter::Trace);
}
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();
}
+18 -18
View File
@@ -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;
}
}
+43 -43
View File
@@ -13,79 +13,79 @@ use crate::state::State;
#[tauri::command]
pub async fn get_validator_nymd_urls(
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ValidatorUrls, BackendError> {
let state = state.read().await;
let urls: Vec<ValidatorUrl> = state.get_nymd_urls(network).collect();
Ok(ValidatorUrls { urls })
let state = state.read().await;
let urls: Vec<ValidatorUrl> = state.get_nymd_urls(network).collect();
Ok(ValidatorUrls { urls })
}
#[tauri::command]
pub async fn get_validator_api_urls(
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ValidatorUrls, BackendError> {
let state = state.read().await;
let urls: Vec<ValidatorUrl> = state.get_api_urls(network).collect();
Ok(ValidatorUrls { urls })
let state = state.read().await;
let urls: Vec<ValidatorUrl> = 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<RwLock<State>>>,
url: &str,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> 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<RwLock<State>>>,
url: &str,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> 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<RwLock<State>>>,
validator: Validator,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> 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<RwLock<State>>>,
validator: Validator,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> 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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> 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(())
}
File diff suppressed because it is too large Load Diff
@@ -13,29 +13,29 @@ use crate::state::State;
#[tauri::command]
pub async fn get_contract_settings(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriContractStateParams, BackendError> {
log::info!(">>> Getting contract settings");
let res = nymd_client!(state).get_contract_settings().await?.into();
log::trace!("<<< {:?}", res);
Ok(res)
log::info!(">>> Getting contract settings");
let res = nymd_client!(state).get_contract_settings().await?.into();
log::trace!("<<< {:?}", res);
Ok(res)
}
#[tauri::command]
pub async fn update_contract_settings(
params: TauriContractStateParams,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
params: TauriContractStateParams,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriContractStateParams, BackendError> {
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
log::info!(
">>> Updating contract settings: {:?}",
mixnet_contract_settings_params
);
nymd_client!(state)
.update_contract_settings(mixnet_contract_settings_params.clone(), fee)
.await?;
let res = mixnet_contract_settings_params.into();
log::trace!("<<< {:?}", res);
Ok(res)
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
log::info!(
">>> Updating contract settings: {:?}",
mixnet_contract_settings_params
);
nymd_client!(state)
.update_contract_settings(mixnet_contract_settings_params.clone(), fee)
.await?;
let res = mixnet_contract_settings_params.into();
log::trace!("<<< {:?}", res);
Ok(res)
}
+124 -124
View File
@@ -8,167 +8,167 @@ use nym_types::mixnode::MixNodeBond;
use nym_types::transaction::TransactionExecuteResult;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::Fee;
use validator_client::nymd::{CosmWasmCoin, Fee};
#[tauri::command]
pub async fn bond_gateway(
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into_minor_cosmwasm_coin()?;
log::info!(
">>> Bond gateway: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
&gateway.identity_key,
pledge,
&pledge_minor,
fee,
);
let res = nymd_client!(state)
.bond_gateway(gateway, owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into();
log::info!(
">>> Bond gateway: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
&gateway.identity_key,
pledge,
&pledge_minor,
fee,
);
let res = nymd_client!(state)
.bond_gateway(gateway, owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn unbond_gateway(
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(">>> Unbond gateway, fee = {:?}", fee);
let res = nymd_client!(state).unbond_gateway(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(">>> Unbond gateway, fee = {:?}", fee);
let res = nymd_client!(state).unbond_gateway(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn bond_mixnode(
mixnode: MixNode,
owner_signature: String,
pledge: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
mixnode: MixNode,
owner_signature: String,
pledge: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into_minor_cosmwasm_coin()?;
log::info!(
">>> Bond mixnode: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
mixnode.identity_key,
pledge,
pledge_minor,
fee,
);
let res = nymd_client!(state)
.bond_mixnode(mixnode, owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into();
log::info!(
">>> Bond mixnode: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
mixnode.identity_key,
pledge,
pledge_minor,
fee,
);
let res = nymd_client!(state)
.bond_mixnode(mixnode, owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn unbond_mixnode(
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(">>> Unbond mixnode, fee = {:?}", fee);
let res = nymd_client!(state).unbond_mixnode(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(">>> Unbond mixnode, fee = {:?}", fee);
let res = nymd_client!(state).unbond_mixnode(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn update_mixnode(
profit_margin_percent: u8,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
profit_margin_percent: u8,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Update mixnode: profit_margin_percent = {}, fee {:?}",
profit_margin_percent,
fee,
);
let res = nymd_client!(state)
.update_mixnode_config(profit_margin_percent, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Update mixnode: profit_margin_percent = {}, fee {:?}",
profit_margin_percent,
fee,
);
let res = nymd_client!(state)
.update_mixnode_config(profit_margin_percent, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn mixnode_bond_details(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Option<MixNodeBond>, BackendError> {
log::info!(">>> Get mixnode bond details");
let guard = state.read().await;
let client = guard.current_client()?;
let bond = client.nymd.owns_mixnode(client.nymd.address()).await?;
let res = MixNodeBond::from_mixnet_contract_mixnode_bond(bond)?;
log::info!(
"<<< identity_key = {:?}",
res.as_ref().map(|r| r.mix_node.identity_key.to_string())
);
log::trace!("<<< {:?}", res);
Ok(res)
log::info!(">>> Get mixnode bond details");
let guard = state.read().await;
let client = guard.current_client()?;
let bond = client.nymd.owns_mixnode(client.nymd.address()).await?;
let res = MixNodeBond::from_mixnet_contract_mixnode_bond(bond)?;
log::info!(
"<<< identity_key = {:?}",
res.as_ref().map(|r| r.mix_node.identity_key.to_string())
);
log::trace!("<<< {:?}", res);
Ok(res)
}
#[tauri::command]
pub async fn gateway_bond_details(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Option<GatewayBond>, BackendError> {
log::info!(">>> Get gateway bond details");
let guard = state.read().await;
let client = guard.current_client()?;
let bond = client.nymd.owns_gateway(client.nymd.address()).await?;
let res = GatewayBond::from_mixnet_contract_gateway_bond(bond)?;
log::info!(
"<<< identity_key = {:?}",
res.as_ref().map(|r| r.gateway.identity_key.to_string())
);
log::trace!("<<< {:?}", res);
Ok(res)
log::info!(">>> Get gateway bond details");
let guard = state.read().await;
let client = guard.current_client()?;
let bond = client.nymd.owns_gateway(client.nymd.address()).await?;
let res = GatewayBond::from_mixnet_contract_gateway_bond(bond)?;
log::info!(
"<<< identity_key = {:?}",
res.as_ref().map(|r| r.gateway.identity_key.to_string())
);
log::trace!("<<< {:?}", res);
Ok(res)
}
#[tauri::command]
pub async fn get_operator_rewards(
address: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
log::info!(">>> Get operator rewards for {}", address);
let denom = state.read().await.current_network().denom();
let rewards_as_minor = nymd_client!(state).get_operator_rewards(address).await?;
let amount: MajorCurrencyAmount =
MajorCurrencyAmount::from_minor_uint128_and_denom(rewards_as_minor, &denom.to_string())?;
log::info!(
"<<< rewards_as_minor = {}, amount = {}",
rewards_as_minor,
amount
);
Ok(amount)
log::info!(">>> Get operator rewards for {}", address);
let denom = state.read().await.current_network().denom();
let rewards_as_minor = nymd_client!(state).get_operator_rewards(address).await?;
let coin = CosmWasmCoin::new(rewards_as_minor.u128(), denom.as_ref());
let amount: MajorCurrencyAmount = coin.into();
log::info!(
"<<< rewards_as_minor = {}, amount = {}",
rewards_as_minor,
amount
);
Ok(amount)
}
@@ -1,373 +1,378 @@
use std::collections::HashMap;
use std::sync::Arc;
use cosmwasm_std::Coin as CosmWasmCoin;
use tokio::sync::RwLock;
use mixnet_contract_common::IdentityKey;
use nym_types::currency::{CurrencyDenom, MajorCurrencyAmount};
use nym_types::delegation::{
from_contract_delegation_events, Delegation, DelegationEvent, DelegationRecord,
DelegationWithEverything, DelegationsSummaryResponse,
};
use nym_types::transaction::TransactionExecuteResult;
use validator_client::nymd::Fee;
use crate::error::BackendError;
use crate::state::State;
use crate::vesting::delegate::get_pending_vesting_delegation_events;
use crate::{api_client, nymd_client};
use cosmwasm_std::Coin as CosmWasmCoin;
use mixnet_contract_common::IdentityKey;
use nym_types::currency::{CurrencyDenom, MajorCurrencyAmount};
use nym_types::delegation::{
from_contract_delegation_events, Delegation, DelegationEvent, DelegationRecord,
DelegationWithEverything, DelegationsSummaryResponse,
};
use nym_types::transaction::TransactionExecuteResult;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::Fee;
#[tauri::command]
pub async fn get_pending_delegation_events(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DelegationEvent>, BackendError> {
log::info!(">>> Get pending delegation events");
let events = nymd_client!(state)
.get_pending_delegation_events(nymd_client!(state).address().to_string(), None)
.await?;
log::info!("<<< {} pending delegation events", events.len());
log::trace!("<<< pending delegation events = {:?}", events);
log::info!(">>> Get pending delegation events");
let events = nymd_client!(state)
.get_pending_delegation_events(nymd_client!(state).address().to_string(), None)
.await?;
log::info!("<<< {} pending delegation events", events.len());
log::trace!("<<< pending delegation events = {:?}", events);
match from_contract_delegation_events(events) {
Ok(res) => Ok(res),
Err(e) => Err(e.into()),
}
match from_contract_delegation_events(events) {
Ok(res) => Ok(res),
Err(e) => Err(e.into()),
}
}
#[tauri::command]
pub async fn delegate_to_mixnode(
identity: &str,
amount: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
identity: &str,
amount: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let delegation: CosmWasmCoin = amount.clone().into_minor_cosmwasm_coin()?;
log::info!(
">>> Delegate to mixnode: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}",
identity,
amount,
delegation,
fee,
);
let res = nymd_client!(state)
.delegate_to_mixnode(identity, &delegation, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
let delegation = amount.clone().into();
log::info!(
">>> Delegate to mixnode: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}",
identity,
amount,
delegation,
fee,
);
let res = nymd_client!(state)
.delegate_to_mixnode(identity, delegation, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn undelegate_from_mixnode(
identity: &str,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
identity: &str,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Undelegate from mixnode: identity_key = {}, fee = {:?}",
identity,
fee
);
let res = nymd_client!(state)
.remove_mixnode_delegation(identity, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Undelegate from mixnode: identity_key = {}, fee = {:?}",
identity,
fee
);
let res = nymd_client!(state)
.remove_mixnode_delegation(identity, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
struct DelegationWithHistory {
pub delegation: Delegation,
pub amount_sum: MajorCurrencyAmount,
pub history: Vec<DelegationRecord>,
pub delegation: Delegation,
pub amount_sum: MajorCurrencyAmount,
pub history: Vec<DelegationRecord>,
}
#[tauri::command]
pub async fn get_all_mix_delegations(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DelegationWithEverything>, BackendError> {
log::info!(">>> Get all mixnode delegations");
todo!("figure out this one after merging")
// TODO: add endpoint to validator API to get a single mix node bond
let mixnodes = api_client!(state).get_mixnodes().await?;
let address = nymd_client!(state).address().to_string();
let denom_minor = state.read().await.current_network().denom();
let denom: CurrencyDenom = denom_minor.clone().try_into()?;
log::info!(" >>> Get delegations");
let delegations = nymd_client!(state)
.get_delegator_delegations_paged(address.clone(), None, None) // get all delegations, ignoring paging
.await?
.delegations;
log::info!(" <<< {} delegations", delegations.len());
// first get pending events from the mixnet contract (operations made with unlocked tokens)
let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?;
// then get pending events from the vesting contract (operations made with locked tokens)
let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?;
for event in pending_vesting_events {
pending_events_for_account.push(event);
}
log::info!(
" <<< {} pending delegation events for account",
pending_events_for_account.len()
);
let mut map: HashMap<String, DelegationWithHistory> = HashMap::new();
for pending_event in pending_events_for_account.clone() {
if delegations
.iter()
.any(|d| d.node_identity == pending_event.node_identity)
{
let amount = pending_event
.amount
.unwrap_or_else(|| MajorCurrencyAmount::zero(&denom));
let delegation = DelegationWithHistory {
delegation: Delegation {
amount: amount.clone(),
node_identity: pending_event.node_identity,
proxy: None,
owner: pending_event.address,
block_height: pending_event.block_height,
},
amount_sum: amount,
history: vec![],
};
map.insert(delegation.delegation.node_identity.clone(), delegation);
}
}
for d in delegations {
// create record of delegation
let delegated_on_iso_datetime = nymd_client!(state)
.get_block_timestamp(Some(d.block_height as u32))
.await?
.to_rfc3339();
let amount: MajorCurrencyAmount = d.amount.clone().try_into()?;
let record = DelegationRecord {
amount: amount.clone(),
block_height: d.block_height,
delegated_on_iso_datetime,
};
let entry = map
.entry(d.node_identity.clone())
.or_insert(DelegationWithHistory {
delegation: d.try_into()?,
history: vec![],
amount_sum: MajorCurrencyAmount::zero(&amount.denom),
});
entry.history.push(record);
entry.amount_sum = entry.amount_sum.clone() + amount;
}
let mut with_everything: Vec<DelegationWithEverything> = vec![];
for item in map {
let d = item.1.delegation;
let history = item.1.history;
let Delegation {
owner,
node_identity,
amount,
block_height,
proxy,
} = d;
log::trace!(
" --- Delegation: node_identity = {}, amount = {}",
node_identity,
amount
);
let mixnode = mixnodes
.iter()
.find(|m| m.mix_node.identity_key == node_identity);
let pledge_amount: Option<MajorCurrencyAmount> =
mixnode.and_then(|m| m.pledge_amount.clone().try_into().ok());
let total_delegation: Option<MajorCurrencyAmount> =
mixnode.and_then(|m| m.total_delegation.clone().try_into().ok());
let profit_margin_percent: Option<u8> = mixnode.map(|m| m.mix_node.profit_margin_percent);
log::trace!(" >>> Get accumulated rewards: address = {}", address);
let accumulated_rewards = match nymd_client!(state)
.get_delegator_rewards(address.clone(), node_identity.clone(), proxy.clone())
.await
{
Ok(rewards) => {
let amount =
MajorCurrencyAmount::from_minor_uint128_and_denom(rewards, denom_minor.as_ref())?;
log::trace!(" <<< rewards = {}, amount = {}", rewards, amount);
Some(amount)
}
Err(_) => {
log::trace!(" <<< no rewards waiting");
None
}
};
let pending_events = filter_pending_events(&node_identity, pending_events_for_account.clone());
log::trace!(
" --- pending events for mixnode = {}",
pending_events.len()
);
log::trace!(
" >>> Get stake saturation: node_identity = {}",
node_identity
);
let stake_saturation = api_client!(state)
.get_mixnode_stake_saturation(&node_identity)
.await
.ok()
.map(|r| r.saturation);
log::trace!(" <<< {:?}", stake_saturation);
log::trace!(
" >>> Get average uptime percentage: node_identity = {}",
node_identity
);
let avg_uptime_percent = api_client!(state)
.get_mixnode_avg_uptime(&node_identity)
.await
.ok()
.map(|r| r.avg_uptime);
log::trace!(" <<< {:?}", avg_uptime_percent);
log::trace!(
" >>> Convert delegated on block height to timestamp: block_height = {}",
d.block_height
);
let timestamp = nymd_client!(state)
.get_block_timestamp(Some(d.block_height as u32))
.await?;
let delegated_on_iso_datetime = timestamp.to_rfc3339();
log::trace!(
" <<< timestamp = {:?}, delegated_on_iso_datetime = {:?}",
timestamp,
delegated_on_iso_datetime
);
with_everything.push(DelegationWithEverything {
owner: owner.to_string(),
node_identity: node_identity.to_string(),
amount: item.1.amount_sum,
block_height,
proxy: proxy.clone(),
delegated_on_iso_datetime,
stake_saturation,
accumulated_rewards,
profit_margin_percent,
pledge_amount,
avg_uptime_percent,
total_delegation,
pending_events,
history,
})
}
log::trace!("<<< {:?}", with_everything);
Ok(with_everything)
// log::info!(">>> Get all mixnode delegations");
//
// // TODO: add endpoint to validator API to get a single mix node bond
// let mixnodes = api_client!(state).get_mixnodes().await?;
//
// let address = nymd_client!(state).address().to_string();
//
// let denom_minor = state.read().await.current_network().denom();
// let denom: CurrencyDenom = denom_minor.clone().try_into()?;
//
// log::info!(" >>> Get delegations");
// let delegations = nymd_client!(state)
// .get_delegator_delegations_paged(address.clone(), None, None) // get all delegations, ignoring paging
// .await?
// .delegations;
// log::info!(" <<< {} delegations", delegations.len());
//
// // first get pending events from the mixnet contract (operations made with unlocked tokens)
// let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?;
//
// // then get pending events from the vesting contract (operations made with locked tokens)
// let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?;
// for event in pending_vesting_events {
// pending_events_for_account.push(event);
// }
//
// log::info!(
// " <<< {} pending delegation events for account",
// pending_events_for_account.len()
// );
//
// let mut map: HashMap<String, DelegationWithHistory> = HashMap::new();
//
// for pending_event in pending_events_for_account.clone() {
// if delegations
// .iter()
// .any(|d| d.node_identity == pending_event.node_identity)
// {
// let amount = pending_event
// .amount
// .unwrap_or_else(|| MajorCurrencyAmount::zero(&denom));
// let delegation = DelegationWithHistory {
// delegation: Delegation {
// amount: amount.clone(),
// node_identity: pending_event.node_identity,
// proxy: None,
// owner: pending_event.address,
// block_height: pending_event.block_height,
// },
// amount_sum: amount,
// history: vec![],
// };
// map.insert(delegation.delegation.node_identity.clone(), delegation);
// }
// }
//
// for d in delegations {
// // create record of delegation
// let delegated_on_iso_datetime = nymd_client!(state)
// .get_block_timestamp(Some(d.block_height as u32))
// .await?
// .to_rfc3339();
// let amount: MajorCurrencyAmount = d.amount.clone().try_into()?;
// let record = DelegationRecord {
// amount: amount.clone(),
// block_height: d.block_height,
// delegated_on_iso_datetime,
// };
//
// let entry = map
// .entry(d.node_identity.clone())
// .or_insert(DelegationWithHistory {
// delegation: d.try_into()?,
// history: vec![],
// amount_sum: MajorCurrencyAmount::zero(&amount.denom),
// });
//
// entry.history.push(record);
// entry.amount_sum = entry.amount_sum.clone() + amount;
// }
//
// let mut with_everything: Vec<DelegationWithEverything> = vec![];
//
// for item in map {
// let d = item.1.delegation;
// let history = item.1.history;
// let Delegation {
// owner,
// node_identity,
// amount,
// block_height,
// proxy,
// } = d;
//
// log::trace!(
// " --- Delegation: node_identity = {}, amount = {}",
// node_identity,
// amount
// );
//
// let mixnode = mixnodes
// .iter()
// .find(|m| m.mix_node.identity_key == node_identity);
//
// let pledge_amount: Option<MajorCurrencyAmount> =
// mixnode.and_then(|m| m.pledge_amount.clone().try_into().ok());
//
// let total_delegation: Option<MajorCurrencyAmount> =
// mixnode.and_then(|m| m.total_delegation.clone().try_into().ok());
//
// let profit_margin_percent: Option<u8> = mixnode.map(|m| m.mix_node.profit_margin_percent);
//
// log::trace!(" >>> Get accumulated rewards: address = {}", address);
// let accumulated_rewards = match nymd_client!(state)
// .get_delegator_rewards(address.clone(), node_identity.clone(), proxy.clone())
// .await
// {
// Ok(rewards) => {
// let amount = MajorCurrencyAmount::from_minor_uint128_and_denom(
// rewards,
// denom_minor.as_ref(),
// )?;
// log::trace!(" <<< rewards = {}, amount = {}", rewards, amount);
// Some(amount)
// }
// Err(_) => {
// log::trace!(" <<< no rewards waiting");
// None
// }
// };
//
// let pending_events =
// filter_pending_events(&node_identity, pending_events_for_account.clone());
// log::trace!(
// " --- pending events for mixnode = {}",
// pending_events.len()
// );
//
// log::trace!(
// " >>> Get stake saturation: node_identity = {}",
// node_identity
// );
// let stake_saturation = api_client!(state)
// .get_mixnode_stake_saturation(&node_identity)
// .await
// .ok()
// .map(|r| r.saturation);
// log::trace!(" <<< {:?}", stake_saturation);
//
// log::trace!(
// " >>> Get average uptime percentage: node_identity = {}",
// node_identity
// );
// let avg_uptime_percent = api_client!(state)
// .get_mixnode_avg_uptime(&node_identity)
// .await
// .ok()
// .map(|r| r.avg_uptime);
// log::trace!(" <<< {:?}", avg_uptime_percent);
//
// log::trace!(
// " >>> Convert delegated on block height to timestamp: block_height = {}",
// d.block_height
// );
// let timestamp = nymd_client!(state)
// .get_block_timestamp(Some(d.block_height as u32))
// .await?;
// let delegated_on_iso_datetime = timestamp.to_rfc3339();
// log::trace!(
// " <<< timestamp = {:?}, delegated_on_iso_datetime = {:?}",
// timestamp,
// delegated_on_iso_datetime
// );
//
// with_everything.push(DelegationWithEverything {
// owner: owner.to_string(),
// node_identity: node_identity.to_string(),
// amount: item.1.amount_sum,
// block_height,
// proxy: proxy.clone(),
// delegated_on_iso_datetime,
// stake_saturation,
// accumulated_rewards,
// profit_margin_percent,
// pledge_amount,
// avg_uptime_percent,
// total_delegation,
// pending_events,
// history,
// })
// }
// log::trace!("<<< {:?}", with_everything);
//
// Ok(with_everything)
}
fn filter_pending_events(
node_identity: &str,
pending_events: Vec<DelegationEvent>,
node_identity: &str,
pending_events: Vec<DelegationEvent>,
) -> Vec<DelegationEvent> {
pending_events
.iter()
.filter(|e| e.node_identity == node_identity)
.cloned()
.collect::<Vec<DelegationEvent>>()
pending_events
.iter()
.filter(|e| e.node_identity == node_identity)
.cloned()
.collect::<Vec<DelegationEvent>>()
}
#[tauri::command]
pub async fn get_delegator_rewards(
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Get delegator rewards: mix_identity = {}, proxy = {:?}",
mix_identity,
proxy
);
let res = nymd_client!(state)
.get_delegator_rewards(address, mix_identity, proxy)
.await?;
let amount = MajorCurrencyAmount::from_minor_uint128_and_denom(res, denom_minor.as_ref())?;
log::info!(">>> res = {}, amount = {}", res, amount);
Ok(amount)
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Get delegator rewards: mix_identity = {}, proxy = {:?}",
mix_identity,
proxy
);
let res = nymd_client!(state)
.get_delegator_rewards(address, mix_identity, proxy)
.await?;
let coin = CosmWasmCoin::new(res.u128(), denom_minor.as_ref());
let amount = coin.into();
log::info!(">>> res = {}, amount = {}", res, amount);
Ok(amount)
}
#[tauri::command]
pub async fn get_delegation_summary(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationsSummaryResponse, BackendError> {
log::info!(">>> Get delegation summary");
let denom_minor = state.read().await.current_network().denom();
let denom: CurrencyDenom = denom_minor.clone().try_into()?;
let delegations = get_all_mix_delegations(state.clone()).await?;
let mut total_delegations = MajorCurrencyAmount::zero(&denom);
let mut total_rewards = MajorCurrencyAmount::zero(&denom);
for d in delegations.clone() {
total_delegations = total_delegations + d.amount;
if let Some(rewards) = d.accumulated_rewards {
total_rewards = total_rewards + rewards;
}
}
log::info!(
"<<< {} delegations, total_delegations = {}, total_rewards = {}",
delegations.len(),
total_delegations,
total_rewards
);
log::trace!("<<< {:?}", delegations);
Ok(DelegationsSummaryResponse {
delegations,
total_delegations,
total_rewards,
})
todo!("figure out this one after merging")
//
// log::info!(">>> Get delegation summary");
//
// let denom_minor = state.read().await.current_network().denom();
// let denom: CurrencyDenom = denom_minor.clone().try_into()?;
//
// let delegations = get_all_mix_delegations(state.clone()).await?;
// let mut total_delegations = MajorCurrencyAmount::zero(&denom);
// let mut total_rewards = MajorCurrencyAmount::zero(&denom);
//
// for d in delegations.clone() {
// total_delegations = total_delegations + d.amount;
// if let Some(rewards) = d.accumulated_rewards {
// total_rewards = total_rewards + rewards;
// }
// }
// log::info!(
// "<<< {} delegations, total_delegations = {}, total_rewards = {}",
// delegations.len(),
// total_delegations,
// total_rewards
// );
// log::trace!("<<< {:?}", delegations);
//
// Ok(DelegationsSummaryResponse {
// delegations,
// total_delegations,
// total_rewards,
// })
}
#[tauri::command]
pub async fn get_all_pending_delegation_events(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DelegationEvent>, BackendError> {
log::info!(">>> Get all pending delegation events");
log::info!(">>> Get all pending delegation events");
// get pending events from mixnet and vesting contract
let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?;
let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?;
// get pending events from mixnet and vesting contract
let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?;
let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?;
// combine them
for event in pending_vesting_events {
pending_events_for_account.push(event);
}
// combine them
for event in pending_vesting_events {
pending_events_for_account.push(event);
}
Ok(pending_events_for_account)
Ok(pending_events_for_account)
}
@@ -7,10 +7,10 @@ use tokio::sync::RwLock;
#[tauri::command]
pub async fn get_current_epoch(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Epoch, BackendError> {
log::info!(">>> Get curren epoch");
let interval = nymd_client!(state).get_current_epoch().await?;
log::info!("<<< curren epoch = {}", interval);
Ok(interval.into())
log::info!(">>> Get curren epoch");
let interval = nymd_client!(state).get_current_epoch().await?;
log::info!("<<< curren epoch = {}", interval);
Ok(interval.into())
}
@@ -6,41 +6,41 @@ use nym_types::transaction::{SendTxResult, TransactionDetails};
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::{AccountId, CosmosCoin, Fee};
use validator_client::nymd::{AccountId, Fee};
#[tauri::command]
pub async fn send(
address: &str,
amount: MajorCurrencyAmount,
memo: String,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: &str,
amount: MajorCurrencyAmount,
memo: String,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<SendTxResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let address = AccountId::from_str(address)?;
let from_address = nymd_client!(state).address().to_string();
let cosmos_amount: CosmosCoin = amount.clone().into_minor_cosmos_coin()?;
log::info!(
">>> Send: amount = {}, minor_amount = {:?}, from = {}, to = {}, fee = {:?}",
amount,
cosmos_amount,
from_address,
address.as_ref(),
fee,
);
let raw_res = nymd_client!(state)
.send(&address, vec![cosmos_amount], memo, fee)
.await?;
log::info!("<<< tx hash = {}", raw_res.hash.to_string());
let res = SendTxResult::new(
raw_res,
TransactionDetails {
from_address,
to_address: address.to_string(),
amount,
},
denom_minor.as_ref(),
)?;
log::trace!("<<< {:?}", res);
Ok(res)
let denom_minor = state.read().await.current_network().denom();
let address = AccountId::from_str(address)?;
let from_address = nymd_client!(state).address().to_string();
let amount2 = amount.clone().into();
log::info!(
">>> Send: amount = {}, minor_amount = {:?}, from = {}, to = {}, fee = {:?}",
amount,
amount2,
from_address,
address.as_ref(),
fee,
);
let raw_res = nymd_client!(state)
.send(&address, vec![amount2], memo, fee)
.await?;
log::info!("<<< tx hash = {}", raw_res.hash.to_string());
let res = SendTxResult::new(
raw_res,
TransactionDetails {
from_address,
to_address: address.to_string(),
amount,
},
denom_minor.as_ref(),
)?;
log::trace!("<<< {:?}", res);
Ok(res)
}
@@ -11,22 +11,22 @@ use tokio::sync::RwLock;
#[tauri::command]
pub async fn simulate_update_contract_settings(
params: TauriContractStateParams,
state: tauri::State<'_, Arc<RwLock<State>>>,
params: TauriContractStateParams,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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()?)
}
@@ -12,26 +12,26 @@ use validator_client::nymd::{AccountId, MsgSend};
#[tauri::command]
pub async fn simulate_send(
address: &str,
amount: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: &str,
amount: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let guard = state.read().await;
let to_address = AccountId::from_str(address)?;
let amount = vec![amount.into_cosmos_coin()?];
let to_address = AccountId::from_str(address)?;
let amount = vec![amount.into()];
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()?)
}
@@ -11,166 +11,166 @@ use tokio::sync::RwLock;
#[tauri::command]
pub async fn simulate_bond_gateway(
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let pledge = pledge.into_cosmos_coin()?;
let guard = state.read().await;
let pledge = pledge.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();
// 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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
mixnode: MixNode,
owner_signature: String,
pledge: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let pledge = pledge.into_cosmos_coin()?;
let guard = state.read().await;
let pledge = pledge.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::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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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<RwLock<State>>>,
profit_margin_percent: u8,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
identity: &str,
amount: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let delegation = amount.into_cosmos_coin()?;
let guard = state.read().await;
let delegation = amount.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::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<RwLock<State>>>,
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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()?)
}
@@ -15,47 +15,45 @@ pub mod vesting;
// technically we could have also exposed a result: Option<AbciResult> 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<GasInfo>,
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<GasInfo>,
pub gas_price: GasPrice,
}
impl SimulateResult {
pub fn new(gas_info: Option<GasInfo>, gas_price: GasPrice) -> Self {
SimulateResult {
gas_info,
gas_price,
pub fn new(gas_info: Option<GasInfo>, gas_price: GasPrice) -> Self {
SimulateResult {
gas_info,
gas_price,
}
}
}
pub fn detailed_fee(&self) -> Result<FeeDetails, TypesError> {
let amount: Option<MajorCurrencyAmount> = match self.to_fee_amount() {
Some(a) => Some(a.try_into()?),
None => None,
};
Ok(FeeDetails {
amount,
fee: self.to_fee(),
})
}
fn to_fee_amount(&self) -> Option<CosmosCoin> {
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()
pub fn detailed_fee(&self) -> Result<FeeDetails, TypesError> {
let amount: Option<MajorCurrencyAmount> = match self.to_fee_amount() {
Some(a) => Some(a.into()),
None => None,
};
Ok(FeeDetails {
amount,
fee: self.to_fee(),
})
})
.unwrap_or_default()
}
}
fn to_fee_amount(&self) -> Option<CosmosCoin> {
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()
}
}
@@ -12,141 +12,141 @@ use vesting_contract_common::ExecuteMsg;
#[tauri::command]
pub async fn simulate_vesting_bond_gateway(
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let pledge = pledge.into_cosmwasm_coin()?;
let guard = state.read().await;
let pledge = pledge.into();
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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
mixnode: MixNode,
owner_signature: String,
pledge: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let pledge = pledge.into_cosmwasm_coin()?;
let guard = state.read().await;
let pledge = pledge.into();
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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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<RwLock<State>>>,
profit_margin_percent: u8,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
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: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
amount: MajorCurrencyAmount,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let amount = amount.into_cosmwasm_coin()?;
let guard = state.read().await;
let amount = amount.into();
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()?)
}
@@ -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<i64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
identity: &str,
since: Option<i64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<CoreNodeStatusResponse, BackendError> {
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<i64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
identity: &str,
since: Option<i64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<CoreNodeStatusResponse, BackendError> {
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<RwLock<State>>>,
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MixnodeStatusResponse, BackendError> {
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<RwLock<State>>>,
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<RewardEstimationResponse, BackendError> {
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<RwLock<State>>>,
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<StakeSaturationResponse, BackendError> {
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<RwLock<State>>>,
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<InclusionProbabilityResponse, BackendError> {
Ok(
api_client!(state)
.get_mixnode_inclusion_probability(identity)
.await?,
)
Ok(api_client!(state)
.get_mixnode_inclusion_probability(identity)
.await?)
}
@@ -11,142 +11,142 @@ use validator_client::nymd::{Fee, VestingSigningClient};
#[tauri::command]
pub async fn vesting_bond_gateway(
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
gateway: Gateway,
pledge: MajorCurrencyAmount,
owner_signature: String,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into_minor_cosmwasm_coin()?;
log::info!(
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into();
log::info!(
">>> Bond gateway with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
gateway.identity_key,
pledge,
pledge_minor,
fee,
);
let res = nymd_client!(state)
.vesting_bond_gateway(gateway, &owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let res = nymd_client!(state)
.vesting_bond_gateway(gateway, &owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn vesting_unbond_gateway(
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Unbond gateway bonded with locked tokens, fee = {:?}",
fee
);
let res = nymd_client!(state).vesting_unbond_gateway(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Unbond gateway bonded with locked tokens, fee = {:?}",
fee
);
let res = nymd_client!(state).vesting_unbond_gateway(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn vesting_bond_mixnode(
mixnode: MixNode,
owner_signature: String,
pledge: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
mixnode: MixNode,
owner_signature: String,
pledge: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into_minor_cosmwasm_coin()?;
log::info!(
let denom_minor = state.read().await.current_network().denom();
let pledge_minor = pledge.clone().into();
log::info!(
">>> Bond mixnode with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
mixnode.identity_key,
pledge,
pledge_minor,
fee
);
let res = nymd_client!(state)
.vesting_bond_mixnode(mixnode, &owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let res = nymd_client!(state)
.vesting_bond_mixnode(mixnode, &owner_signature, pledge_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn vesting_unbond_mixnode(
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Unbond mixnode bonded with locked tokens, fee = {:?}",
fee
);
let res = nymd_client!(state).vesting_unbond_mixnode(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Unbond mixnode bonded with locked tokens, fee = {:?}",
fee
);
let res = nymd_client!(state).vesting_unbond_mixnode(fee).await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn withdraw_vested_coins(
amount: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
amount: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let amount_minor = amount.clone().into_minor_cosmwasm_coin()?;
log::info!(
">>> Withdraw vested liquid coins: amount = {}, amount_minor = {}, fee = {:?}",
amount,
amount_minor,
fee
);
let res = nymd_client!(state)
.withdraw_vested_coins(amount_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
let amount_minor = amount.clone().into();
log::info!(
">>> Withdraw vested liquid coins: amount = {}, amount_minor = {}, fee = {:?}",
amount,
amount_minor,
fee
);
let res = nymd_client!(state)
.withdraw_vested_coins(amount_minor, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn vesting_update_mixnode(
profit_margin_percent: u8,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
profit_margin_percent: u8,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Update mixnode bonded with locked tokens: profit_margin_percent = {}, fee = {:?}",
profit_margin_percent,
fee,
);
let res = nymd_client!(state)
.vesting_update_mixnode_config(profit_margin_percent, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Update mixnode bonded with locked tokens: profit_margin_percent = {}, fee = {:?}",
profit_margin_percent,
fee,
);
let res = nymd_client!(state)
.vesting_update_mixnode_config(profit_margin_percent, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
@@ -13,76 +13,76 @@ use crate::state::State;
#[tauri::command]
pub async fn get_pending_vesting_delegation_events(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DelegationEvent>, BackendError> {
log::info!(">>> Get pending delegations from vesting contract");
log::info!(">>> Get pending delegations from vesting contract");
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()?;
let events = client
.get_pending_delegation_events(
client.address().to_string(),
Some(vesting_contract.to_string()),
)
.await?;
let events = client
.get_pending_delegation_events(
client.address().to_string(),
Some(vesting_contract.to_string()),
)
.await?;
log::info!("<<< {} events", events.len());
log::trace!("<<< {:?}", events);
log::info!("<<< {} events", events.len());
log::trace!("<<< {:?}", events);
match from_contract_delegation_events(events) {
Ok(res) => Ok(res),
Err(e) => Err(e.into()),
}
match from_contract_delegation_events(events) {
Ok(res) => Ok(res),
Err(e) => Err(e.into()),
}
}
#[tauri::command]
pub async fn vesting_delegate_to_mixnode(
identity: &str,
amount: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
identity: &str,
amount: MajorCurrencyAmount,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
let delegation = amount.clone().into_minor_cosmwasm_coin()?;
log::info!(
let denom_minor = state.read().await.current_network().denom();
let delegation = amount.clone().into();
log::info!(
">>> Delegate to mixnode with locked tokens: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}",
identity,
amount,
delegation,
fee
);
let res = nymd_client!(state)
.vesting_delegate_to_mixnode(identity, &delegation, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let res = nymd_client!(state)
.vesting_delegate_to_mixnode(identity, delegation, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
#[tauri::command]
pub async fn vesting_undelegate_from_mixnode(
identity: &str,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
identity: &str,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TransactionExecuteResult, BackendError> {
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Undelegate from mixnode delegated with locked tokens: identity_key = {}, fee = {:?}",
identity,
fee,
);
let res = nymd_client!(state)
.vesting_undelegate_from_mixnode(identity, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
let denom_minor = state.read().await.current_network().denom();
log::info!(
">>> Undelegate from mixnode delegated with locked tokens: identity_key = {}, fee = {:?}",
identity,
fee,
);
let res = nymd_client!(state)
.vesting_undelegate_from_mixnode(identity, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res,
denom_minor.as_ref(),
)?)
}
@@ -15,201 +15,201 @@ use crate::state::State;
#[tauri::command]
pub async fn locked_coins(
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
log::info!(">>> Query locked coins");
let res = nymd_client!(state)
.locked_coins(
nymd_client!(state).address().as_ref(),
block_time.map(Timestamp::from_seconds),
)
.await?
.try_into()?;
log::info!("<<< locked coins = {}", res);
Ok(res)
log::info!(">>> Query locked coins");
let res = nymd_client!(state)
.locked_coins(
nymd_client!(state).address().as_ref(),
block_time.map(Timestamp::from_seconds),
)
.await?
.into();
log::info!("<<< locked coins = {}", res);
Ok(res)
}
#[tauri::command]
pub async fn spendable_coins(
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
log::info!(">>> Query spendable coins");
let res = nymd_client!(state)
.spendable_coins(
nymd_client!(state).address().as_ref(),
block_time.map(Timestamp::from_seconds),
)
.await?
.try_into()?;
log::info!("<<< spendable coins = {}", res);
Ok(res)
log::info!(">>> Query spendable coins");
let res = nymd_client!(state)
.spendable_coins(
nymd_client!(state).address().as_ref(),
block_time.map(Timestamp::from_seconds),
)
.await?
.into();
log::info!("<<< spendable coins = {}", res);
Ok(res)
}
#[tauri::command]
pub async fn vested_coins(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
log::info!(">>> Query vested coins");
let res = nymd_client!(state)
.vested_coins(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.try_into()?;
log::info!("<<< vested coins = {}", res);
Ok(res)
log::info!(">>> Query vested coins");
let res = nymd_client!(state)
.vested_coins(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into();
log::info!("<<< vested coins = {}", res);
Ok(res)
}
#[tauri::command]
pub async fn vesting_coins(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
log::info!(">>> Query vesting coins");
let res = nymd_client!(state)
.vesting_coins(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.try_into()?;
log::info!("<<< vesting coins = {}", res);
Ok(res)
log::info!(">>> Query vesting coins");
let res = nymd_client!(state)
.vesting_coins(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into();
log::info!("<<< vesting coins = {}", res);
Ok(res)
}
#[tauri::command]
pub async fn vesting_start_time(
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<u64, BackendError> {
log::info!(">>> Query vesting start time");
let res = nymd_client!(state)
.vesting_start_time(vesting_account_address)
.await?
.seconds();
log::info!("<<< vesting start time = {}", res);
Ok(res)
log::info!(">>> Query vesting start time");
let res = nymd_client!(state)
.vesting_start_time(vesting_account_address)
.await?
.seconds();
log::info!("<<< vesting start time = {}", res);
Ok(res)
}
#[tauri::command]
pub async fn vesting_end_time(
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<u64, BackendError> {
log::info!(">>> Query vesting end time");
let res = nymd_client!(state)
.vesting_end_time(vesting_account_address)
.await?
.seconds();
log::info!("<<< vesting end time = {}", res);
Ok(res)
log::info!(">>> Query vesting end time");
let res = nymd_client!(state)
.vesting_end_time(vesting_account_address)
.await?
.seconds();
log::info!("<<< vesting end time = {}", res);
Ok(res)
}
#[tauri::command]
pub async fn original_vesting(
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<OriginalVestingResponse, BackendError> {
log::info!(">>> Query original vesting");
let res = nymd_client!(state)
.original_vesting(vesting_account_address)
.await?
.try_into()?;
log::info!("<<< {:?}", res);
Ok(res)
log::info!(">>> Query original vesting");
let res = nymd_client!(state)
.original_vesting(vesting_account_address)
.await?
.try_into()?;
log::info!("<<< {:?}", res);
Ok(res)
}
#[tauri::command]
pub async fn delegated_free(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
log::info!(">>> Query delegated free");
let res = nymd_client!(state)
.delegated_free(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.try_into()?;
log::info!("<<< delegated free = {}", res);
Ok(res)
log::info!(">>> Query delegated free");
let res = nymd_client!(state)
.delegated_free(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into();
log::info!("<<< delegated free = {}", res);
Ok(res)
}
/// Returns the total amount of delegated tokens that have vested
#[tauri::command]
pub async fn delegated_vesting(
block_time: Option<u64>,
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
block_time: Option<u64>,
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MajorCurrencyAmount, BackendError> {
log::info!(">>> Query delegated vesting");
let res = nymd_client!(state)
.delegated_vesting(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.try_into()?;
log::info!("<<< delegated_vesting = {}", res);
Ok(res)
log::info!(">>> Query delegated vesting");
let res = nymd_client!(state)
.delegated_vesting(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into();
log::info!("<<< delegated_vesting = {}", res);
Ok(res)
}
#[tauri::command]
pub async fn vesting_get_mixnode_pledge(
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Option<PledgeData>, BackendError> {
log::info!(">>> Query vesting get mixnode pledge");
let res = nymd_client!(state)
.get_mixnode_pledge(address)
.await?
.and_then(PledgeData::and_then);
log::info!("<<< {:?}", res);
Ok(res)
log::info!(">>> Query vesting get mixnode pledge");
let res = nymd_client!(state)
.get_mixnode_pledge(address)
.await?
.and_then(PledgeData::and_then);
log::info!("<<< {:?}", res);
Ok(res)
}
#[tauri::command]
pub async fn vesting_get_gateway_pledge(
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Option<PledgeData>, BackendError> {
log::info!(">>> Query vesting get gateway pledge");
let res = nymd_client!(state)
.get_gateway_pledge(address)
.await?
.and_then(PledgeData::and_then);
log::info!("<<< {:?}", res);
Ok(res)
log::info!(">>> Query vesting get gateway pledge");
let res = nymd_client!(state)
.get_gateway_pledge(address)
.await?
.and_then(PledgeData::and_then);
log::info!("<<< {:?}", res);
Ok(res)
}
#[tauri::command]
pub async fn get_current_vesting_period(
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Period, BackendError> {
log::info!(">>> Query current vesting period");
let res = nymd_client!(state)
.get_current_vesting_period(address)
.await?;
log::info!("<<< {:?}", res);
Ok(res)
log::info!(">>> Query current vesting period");
let res = nymd_client!(state)
.get_current_vesting_period(address)
.await?;
log::info!("<<< {:?}", res);
Ok(res)
}
#[tauri::command]
pub async fn get_account_info(
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<VestingAccountInfo, BackendError> {
log::info!(">>> Query account info");
let res = nymd_client!(state).get_account(address).await?.try_into()?;
log::info!("<<< {:?}", res);
Ok(res)
log::info!(">>> Query account info");
let res = nymd_client!(state).get_account(address).await?.try_into()?;
log::info!("<<< {:?}", res);
Ok(res)
}
+348 -358
View File
@@ -18,450 +18,440 @@ use std::time::Duration;
// Some hardcoded metadata overrides
static METADATA_OVERRIDES: Lazy<Vec<(Url, ValidatorMetadata)>> = 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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> 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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> 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<Network, Client<SigningNymdClient>>,
current_network: Network,
config: config::Config,
signing_clients: HashMap<Network, Client<SigningNymdClient>>,
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<WalletAccountIds>,
// 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<WalletAccountIds>,
/// 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<Url, ValidatorMetadata>,
/// We fetch (and cache) some metadata, such as names, when available
validator_metadata: HashMap<Url, ValidatorMetadata>,
}
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<Network, CosmosAccountId>,
// The wallet account id
pub id: crate::wallet_storage::AccountId,
// The set of corresponding network identities derived from the mnemonic
pub addresses: HashMap<Network, CosmosAccountId>,
}
impl State {
pub fn client(&self, network: Network) -> Result<&Client<SigningNymdClient>, BackendError> {
self
.signing_clients
.get(&network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn client(&self, network: Network) -> Result<&Client<SigningNymdClient>, BackendError> {
self.signing_clients
.get(&network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn client_mut(
&mut self,
network: Network,
) -> Result<&mut Client<SigningNymdClient>, BackendError> {
self
.signing_clients
.get_mut(&network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn client_mut(
&mut self,
network: Network,
) -> Result<&mut Client<SigningNymdClient>, BackendError> {
self.signing_clients
.get_mut(&network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn current_client(&self) -> Result<&Client<SigningNymdClient>, BackendError> {
self
.signing_clients
.get(&self.current_network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn current_client(&self) -> Result<&Client<SigningNymdClient>, BackendError> {
self.signing_clients
.get(&self.current_network)
.ok_or(BackendError::ClientNotInitialized)
}
#[allow(unused)]
pub fn current_client_mut(&mut self) -> Result<&mut Client<SigningNymdClient>, 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<SigningNymdClient>, 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<SigningNymdClient>) {
self.signing_clients.insert(network, client);
}
pub fn add_client(&mut self, network: Network, client: Client<SigningNymdClient>) {
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<WalletAccountIds>) {
self.all_accounts = all_accounts
}
pub(crate) fn set_all_accounts(&mut self, all_accounts: Vec<WalletAccountIds>) {
self.all_accounts = all_accounts
}
pub(crate) fn get_all_accounts(&self) -> impl Iterator<Item = &WalletAccountIds> {
self.all_accounts.iter()
}
pub(crate) fn get_all_accounts(&self) -> impl Iterator<Item = &WalletAccountIds> {
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<Item = config::ValidatorConfigEntry> + '_ {
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<Item = config::ValidatorConfigEntry> + '_ {
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<Item = Url> + '_ {
self
.get_config_validator_entries(network)
.into_iter()
.map(|v| v.nymd_url)
}
pub fn get_api_urls_only(&self, network: Network) -> impl Iterator<Item = Url> + '_ {
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<Item = network_config::ValidatorUrl> + '_ {
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<Item = network_config::ValidatorUrl> + '_ {
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, Vec<Url>> {
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<Item = Url> + '_ {
self.get_config_validator_entries(network)
.into_iter()
.map(|v| v.nymd_url)
}
pub fn get_all_api_urls(&self) -> HashMap<Network, Vec<Url>> {
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<Item = Url> + '_ {
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<Item = network_config::ValidatorUrl> + '_ {
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<Item = network_config::ValidatorUrl> + '_ {
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, Vec<Url>> {
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, Vec<Url>> {
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<(Url, String), reqwest::Error>>,
) -> 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<(Url, String), reqwest::Error>>,
) -> 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<Item = Url>,
nymd_urls: impl Iterator<Item = Url>,
) -> Result<Vec<Result<(Url, String), reqwest::Error>>, 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<String>,
pub name: Option<String>,
}
#[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::<Vec<_>>();
#[test]
fn adding_validators_urls_prepends() {
let mut state = State::default();
let _api_urls = state.get_api_urls(Network::MAINNET).collect::<Vec<_>>();
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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(),
],
)
}
}
+18 -22
View File
@@ -6,40 +6,36 @@ use std::sync::Arc;
use tokio::sync::RwLock;
fn get_env_as_option(key: &str) -> Option<String> {
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 async fn owns_mixnode(
state: tauri::State<'_, Arc<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<bool, BackendError> {
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<RwLock<State>>>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<bool, BackendError> {
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())
}
@@ -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<EncryptedLogin>,
version: u32,
accounts: Vec<EncryptedLogin>,
}
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<StoredLogin>, 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<EncryptedLogin> {
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<StoredLogin, BackendError> {
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<Vec<StoredLogin>, BackendError> {
self
.accounts
.iter()
.map(|account| account.account.decrypt_struct(password))
.collect::<Result<Vec<_>, _>>()
}
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<StoredLogin>, 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<EncryptedLogin> {
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<StoredLogin, BackendError> {
self.get_encrypted_login(id)?.decrypt_struct(password)
}
pub fn decrypt_all(&self, password: &UserPassword) -> Result<Vec<StoredLogin>, BackendError> {
self.accounts
.iter()
.map(|account| account.account.decrypt_struct(password))
.collect::<Result<Vec<_>, _>>()
}
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<StoredLogin>,
pub id: LoginId,
pub account: EncryptedData<StoredLogin>,
}
impl EncryptedLogin {
pub(crate) fn encrypt(
id: LoginId,
login: &StoredLogin,
password: &UserPassword,
) -> Result<Self, BackendError> {
Ok(EncryptedLogin {
id,
account: super::encryption::encrypt_struct(login, password)?,
})
}
pub(crate) fn encrypt(
id: LoginId,
login: &StoredLogin,
password: &UserPassword,
) -> Result<Self, BackendError> {
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<WalletAccount>,
accounts: Vec<WalletAccount>,
}
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<Item = &WalletAccount> {
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<Item = WalletAccount> {
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<Item = &WalletAccount> {
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<Item = WalletAccount> {
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<Vec<WalletAccount>> for MultipleAccounts {
fn from(accounts: Vec<WalletAccount>) -> MultipleAccounts {
Self { accounts }
}
fn from(accounts: Vec<WalletAccount>) -> 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<S: Serializer>(
hd_path: &DerivationPath,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(hd_path)
}
pub fn serialize<S: Serializer>(
hd_path: &DerivationPath,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(hd_path)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<DerivationPath, D::Error> {
let s = <&str>::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<DerivationPath, D::Error> {
let s = <&str>::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
@@ -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<T> {
#[serde(with = "base64")]
ciphertext: Vec<u8>,
#[serde(with = "base64")]
salt: Vec<u8>,
#[serde(with = "base64")]
iv: Vec<u8>,
#[serde(with = "base64")]
ciphertext: Vec<u8>,
#[serde(with = "base64")]
salt: Vec<u8>,
#[serde(with = "base64")]
iv: Vec<u8>,
#[serde(skip)]
#[zeroize(skip)]
_marker: PhantomData<T>,
#[serde(skip)]
#[zeroize(skip)]
_marker: PhantomData<T>,
}
impl<T> Drop for EncryptedData<T> {
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<T> EncryptedData<T> {
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<u8> 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<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&base64::encode(bytes))
}
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&base64::encode(bytes))
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let s = <String>::deserialize(deserializer)?;
base64::decode(&s).map_err(serde::de::Error::custom)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let s = <String>::deserialize(deserializer)?;
base64::decode(&s).map_err(serde::de::Error::custom)
}
}
impl<T> EncryptedData<T> {
#[allow(unused)]
pub(crate) fn encrypt_struct(data: &T, password: &UserPassword) -> Result<Self, BackendError>
where
T: Serialize,
{
encrypt_struct(data, password)
}
#[allow(unused)]
pub(crate) fn encrypt_struct(data: &T, password: &UserPassword) -> Result<Self, BackendError>
where
T: Serialize,
{
encrypt_struct(data, password)
}
pub(crate) fn decrypt_struct(&self, password: &UserPassword) -> Result<T, BackendError>
where
T: for<'a> Deserialize<'a>,
{
decrypt_struct(self, password)
}
pub(crate) fn decrypt_struct(&self, password: &UserPassword) -> Result<T, BackendError>
where
T: for<'a> Deserialize<'a>,
{
decrypt_struct(self, password)
}
}
impl EncryptedData<Vec<u8>> {
#[allow(unused)]
pub(crate) fn encrypt_data(data: &[u8], password: &UserPassword) -> Result<Self, BackendError> {
encrypt_data(data, password)
}
#[allow(unused)]
pub(crate) fn encrypt_data(data: &[u8], password: &UserPassword) -> Result<Self, BackendError> {
encrypt_data(data, password)
}
#[allow(unused)]
pub(crate) fn decrypt_data(&self, password: &UserPassword) -> Result<Vec<u8>, BackendError> {
decrypt_data(self, password)
}
#[allow(unused)]
pub(crate) fn decrypt_data(&self, password: &UserPassword) -> Result<Vec<u8>, BackendError> {
decrypt_data(self, password)
}
}
fn derive_cipher_key<KeySize>(
password: &UserPassword,
salt: &[u8],
password: &UserPassword,
salt: &[u8],
) -> Result<Key<KeySize>, BackendError>
where
KeySize: ArrayLength<u8>,
KeySize: ArrayLength<u8>,
{
// 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<u8>, Vec<u8>) {
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<Vec<u8>, 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<Vec<u8>, 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<EncryptedData<Vec<u8>>, 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<T>(
data: &T,
password: &UserPassword,
data: &T,
password: &UserPassword,
) -> Result<EncryptedData<T>, 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<Vec<u8>>,
password: &UserPassword,
encrypted_data: &EncryptedData<Vec<u8>>,
password: &UserPassword,
) -> Result<Vec<u8>, 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<T>(
encrypted_data: &EncryptedData<T>,
password: &UserPassword,
encrypted_data: &EncryptedData<T>,
password: &UserPassword,
) -> Result<T, BackendError>
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());
}
}
File diff suppressed because it is too large Load Diff
@@ -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<str> for LoginId {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl From<String> 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<str> for AccountId {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl From<String> 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<LoginId> 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<str> for UserPassword {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
+6 -8
View File
@@ -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,
};
+3 -3
View File
@@ -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<C> Client<C> {
&self,
rewarded_set: Vec<IdentityKey>,
expected_active_set_size: u32,
reward_msgs: Vec<(ExecuteMsg, Vec<CosmosCoin>)>,
reward_msgs: Vec<(ExecuteMsg, Vec<Coin>)>,
) -> Result<(), RewardingError>
where
C: SigningCosmWasmClient + Sync,
@@ -358,7 +358,7 @@ impl<C> Client<C> {
async fn execute_multiple_with_retry<M>(
&self,
msgs: Vec<(M, Vec<CosmosCoin>)>,
msgs: Vec<(M, Vec<Coin>)>,
fee: Fee,
memo: String,
) -> Result<(), RewardingError>
@@ -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<Vec<(ExecuteMsg, Vec<CosmosCoin>)>, RewardingError> {
) -> Result<Vec<(ExecuteMsg, Vec<Coin>)>, 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<Vec<(ExecuteMsg, Vec<CosmosCoin>)>, RewardingError> {
) -> Result<Vec<(ExecuteMsg, Vec<Coin>)>, RewardingError> {
cfg_if::cfg_if! {
if #[cfg(feature = "no-reward")] {
Ok(vec![])
@@ -52,6 +52,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,