Merge with develop
This commit is contained in:
@@ -9,12 +9,18 @@
|
||||
- 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])
|
||||
@@ -29,6 +35,8 @@
|
||||
[#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
|
||||
|
||||
## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04)
|
||||
|
||||
|
||||
@@ -803,6 +803,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,
|
||||
|
||||
@@ -11,6 +11,28 @@ use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, Vesting
|
||||
|
||||
#[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,
|
||||
@@ -375,4 +397,78 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
)
|
||||
.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>,
|
||||
|
||||
@@ -318,6 +318,14 @@ impl NodeRewardResult {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RewardEstimate {
|
||||
pub total_node_reward: u64,
|
||||
pub operator_reward: u64,
|
||||
pub delegators_reward: u64,
|
||||
pub node_profit: u64,
|
||||
pub operator_cost: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
pub pledge_amount: Coin,
|
||||
@@ -410,63 +418,80 @@ impl MixNodeBond {
|
||||
/ U128::from_num(circulating_supply)
|
||||
}
|
||||
|
||||
pub fn lambda_ticked(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a bond to the token circulating supply
|
||||
self.lambda(params).min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn lambda(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a bond to the token circulating supply
|
||||
let pledge_to_circulating_supply_ratio =
|
||||
self.pledge_to_circulating_supply(params.circulating_supply());
|
||||
pledge_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
self.pledge_to_circulating_supply(params.circulating_supply())
|
||||
}
|
||||
|
||||
pub fn sigma_ticked(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a delegation to the the token circulating supply
|
||||
self.sigma(params).min(params.one_over_k())
|
||||
}
|
||||
|
||||
pub fn sigma(&self, params: &RewardParams) -> U128 {
|
||||
// Ratio of a delegation to the the token circulating supply
|
||||
let total_bond_to_circulating_supply_ratio =
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply());
|
||||
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
|
||||
self.total_bond_to_circulating_supply(params.circulating_supply())
|
||||
}
|
||||
|
||||
pub fn estimate_reward(
|
||||
&self,
|
||||
params: &RewardParams,
|
||||
) -> Result<(u64, u64, u64), MixnetContractError> {
|
||||
) -> Result<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 {
|
||||
@@ -474,11 +499,9 @@ impl MixNodeBond {
|
||||
if reward.sigma == 0 {
|
||||
return 0;
|
||||
}
|
||||
let profit = if reward.reward < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward.reward - params.node.operator_cost()
|
||||
};
|
||||
|
||||
let profit = reward.reward.saturating_sub(params.node.operator_cost());
|
||||
|
||||
let operator_base_reward = reward.reward.min(params.node.operator_cost());
|
||||
// Div by zero checked above
|
||||
let operator_reward = (self.profit_margin()
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(¶ms);
|
||||
|
||||
@@ -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(¶ms).int(), 203558u128);
|
||||
assert_eq!(mix_1.node_profit(¶ms).int(), 183202u128);
|
||||
|
||||
assert_ne!(
|
||||
mix_1_reward_result.reward(),
|
||||
mix_1.node_profit(¶ms).int()
|
||||
);
|
||||
|
||||
let mix1_operator_reward = mix_1.operator_reward(¶ms);
|
||||
|
||||
@@ -1417,9 +1606,9 @@ pub mod tests {
|
||||
|
||||
let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), ¶ms);
|
||||
|
||||
assert_eq!(mix1_operator_reward, 167513);
|
||||
assert_eq!(mix1_delegator1_reward, 73280);
|
||||
assert_eq!(mix1_delegator2_reward, 18320);
|
||||
assert_eq!(mix1_operator_reward, 150761);
|
||||
assert_eq!(mix1_delegator1_reward, 65952);
|
||||
assert_eq!(mix1_delegator2_reward, 16488);
|
||||
|
||||
assert_eq!(
|
||||
mix_1_reward_result.reward().int(),
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,3 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
+269
-269
@@ -17,328 +17,328 @@ const MINOR_IN_MAJOR: f64 = 1_000_000.;
|
||||
#[cfg_attr(test, ts(export, export, export_to = "../src/types/rust/denom.ts"))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub enum Denom {
|
||||
Major,
|
||||
Minor,
|
||||
Major,
|
||||
Minor,
|
||||
}
|
||||
|
||||
impl FromStr for Denom {
|
||||
type Err = BackendError;
|
||||
type Err = BackendError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Denom, BackendError> {
|
||||
let s = s.to_lowercase();
|
||||
for network in Network::iter() {
|
||||
let denom = network.denom();
|
||||
if s == denom.to_lowercase() || s == "minor" {
|
||||
return Ok(Denom::Minor);
|
||||
} else if s == denom[1..].to_lowercase() || s == "major" {
|
||||
return Ok(Denom::Major);
|
||||
}
|
||||
fn from_str(s: &str) -> Result<Denom, BackendError> {
|
||||
let s = s.to_lowercase();
|
||||
for network in Network::iter() {
|
||||
let denom = network.denom();
|
||||
if s == denom.to_lowercase() || s == "minor" {
|
||||
return Ok(Denom::Minor);
|
||||
} else if s == denom[1..].to_lowercase() || s == "major" {
|
||||
return Ok(Denom::Major);
|
||||
}
|
||||
}
|
||||
Err(BackendError::InvalidDenom(s))
|
||||
}
|
||||
Err(BackendError::InvalidDenom(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CosmosDenom> for Denom {
|
||||
type Error = BackendError;
|
||||
type Error = BackendError;
|
||||
|
||||
fn try_from(value: CosmosDenom) -> Result<Self, Self::Error> {
|
||||
Denom::from_str(&value.to_string())
|
||||
}
|
||||
fn try_from(value: CosmosDenom) -> Result<Self, Self::Error> {
|
||||
Denom::from_str(&value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/coin.ts"))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct Coin {
|
||||
amount: String,
|
||||
denom: Denom,
|
||||
amount: String,
|
||||
denom: Denom,
|
||||
}
|
||||
|
||||
// Allows adding minor and major denominations, output will have the LHS denom.
|
||||
impl Add for Coin {
|
||||
type Output = Self;
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
let denom = self.denom.clone();
|
||||
let lhs = self.to_minor();
|
||||
let rhs = rhs.to_minor();
|
||||
let lhs_amount = lhs.amount.parse::<u128>().unwrap();
|
||||
let rhs_amount = rhs.amount.parse::<u128>().unwrap();
|
||||
let amount = lhs_amount + rhs_amount;
|
||||
let coin = Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Minor,
|
||||
};
|
||||
match denom {
|
||||
Denom::Major => coin.to_major(),
|
||||
Denom::Minor => coin,
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
let denom = self.denom.clone();
|
||||
let lhs = self.to_minor();
|
||||
let rhs = rhs.to_minor();
|
||||
let lhs_amount = lhs.amount.parse::<u128>().unwrap();
|
||||
let rhs_amount = rhs.amount.parse::<u128>().unwrap();
|
||||
let amount = lhs_amount + rhs_amount;
|
||||
let coin = Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Minor,
|
||||
};
|
||||
match denom {
|
||||
Denom::Major => coin.to_major(),
|
||||
Denom::Minor => coin,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allows adding minor and major denominations, output will have the LHS denom.
|
||||
impl Sub for Coin {
|
||||
type Output = Self;
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
let denom = self.denom.clone();
|
||||
let lhs = self.to_minor();
|
||||
let rhs = rhs.to_minor();
|
||||
let lhs_amount = lhs.amount.parse::<u128>().unwrap();
|
||||
let rhs_amount = rhs.amount.parse::<u128>().unwrap();
|
||||
let amount = lhs_amount - rhs_amount;
|
||||
let coin = Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Minor,
|
||||
};
|
||||
match denom {
|
||||
Denom::Major => coin.to_major(),
|
||||
Denom::Minor => coin,
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
let denom = self.denom.clone();
|
||||
let lhs = self.to_minor();
|
||||
let rhs = rhs.to_minor();
|
||||
let lhs_amount = lhs.amount.parse::<u128>().unwrap();
|
||||
let rhs_amount = rhs.amount.parse::<u128>().unwrap();
|
||||
let amount = lhs_amount - rhs_amount;
|
||||
let coin = Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Minor,
|
||||
};
|
||||
match denom {
|
||||
Denom::Major => coin.to_major(),
|
||||
Denom::Minor => coin,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Coin {
|
||||
#[allow(unused)]
|
||||
pub fn major<T: ToString>(amount: T) -> Coin {
|
||||
Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Major,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn minor<T: ToString>(amount: T) -> Coin {
|
||||
Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Minor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new<T: ToString>(amount: T, denom: &Denom) -> Coin {
|
||||
Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: denom.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_major(&self) -> Coin {
|
||||
match self.denom {
|
||||
Denom::Major => self.clone(),
|
||||
Denom::Minor => Coin {
|
||||
amount: (self.amount.parse::<f64>().unwrap() / MINOR_IN_MAJOR).to_string(),
|
||||
denom: Denom::Major,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_minor(&self) -> Coin {
|
||||
match self.denom {
|
||||
Denom::Minor => self.clone(),
|
||||
Denom::Major => Coin {
|
||||
amount: (self.amount.parse::<f64>().unwrap() * MINOR_IN_MAJOR).to_string(),
|
||||
denom: Denom::Minor,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amount(&self) -> String {
|
||||
self.amount.clone()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn denom(&self) -> Denom {
|
||||
self.denom.clone()
|
||||
}
|
||||
|
||||
// Helper function that returns the local denom in terms of the specified network denom.
|
||||
fn denom_as_string(&self, network_denom: &str) -> Result<String, BackendError> {
|
||||
// Currently there is the widespread assumption that network denomination is always in
|
||||
// `Denom::Minor`, and starts with 'u'.
|
||||
let network_denom = network_denom.to_owned();
|
||||
if !network_denom.starts_with('u') {
|
||||
return Err(BackendError::InvalidNetworkDenom(network_denom));
|
||||
#[allow(unused)]
|
||||
pub fn major<T: ToString>(amount: T) -> Coin {
|
||||
Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Major,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(match &self.denom {
|
||||
Denom::Minor => network_denom,
|
||||
Denom::Major => network_denom[1..].to_string(),
|
||||
})
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn minor<T: ToString>(amount: T) -> Coin {
|
||||
Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: Denom::Minor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_backend_coin(self, network_denom: &str) -> Result<BackendCoin, BackendError> {
|
||||
Ok(BackendCoin::new(
|
||||
self.amount.parse()?,
|
||||
self.denom_as_string(network_denom)?,
|
||||
))
|
||||
}
|
||||
pub fn new<T: ToString>(amount: T, denom: &Denom) -> Coin {
|
||||
Coin {
|
||||
amount: amount.to_string(),
|
||||
denom: denom.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_major(&self) -> Coin {
|
||||
match self.denom {
|
||||
Denom::Major => self.clone(),
|
||||
Denom::Minor => Coin {
|
||||
amount: (self.amount.parse::<f64>().unwrap() / MINOR_IN_MAJOR).to_string(),
|
||||
denom: Denom::Major,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_minor(&self) -> Coin {
|
||||
match self.denom {
|
||||
Denom::Minor => self.clone(),
|
||||
Denom::Major => Coin {
|
||||
amount: (self.amount.parse::<f64>().unwrap() * MINOR_IN_MAJOR).to_string(),
|
||||
denom: Denom::Minor,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amount(&self) -> String {
|
||||
self.amount.clone()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn denom(&self) -> Denom {
|
||||
self.denom.clone()
|
||||
}
|
||||
|
||||
// Helper function that returns the local denom in terms of the specified network denom.
|
||||
fn denom_as_string(&self, network_denom: &str) -> Result<String, BackendError> {
|
||||
// Currently there is the widespread assumption that network denomination is always in
|
||||
// `Denom::Minor`, and starts with 'u'.
|
||||
let network_denom = network_denom.to_owned();
|
||||
if !network_denom.starts_with('u') {
|
||||
return Err(BackendError::InvalidNetworkDenom(network_denom));
|
||||
}
|
||||
|
||||
Ok(match &self.denom {
|
||||
Denom::Minor => network_denom,
|
||||
Denom::Major => network_denom[1..].to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_backend_coin(self, network_denom: &str) -> Result<BackendCoin, BackendError> {
|
||||
Ok(BackendCoin::new(
|
||||
self.amount.parse()?,
|
||||
self.denom_as_string(network_denom)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BackendCoin> for Coin {
|
||||
fn from(c: BackendCoin) -> Self {
|
||||
Coin {
|
||||
amount: c.amount.to_string(),
|
||||
denom: Denom::from_str(c.denom.as_ref()).unwrap(),
|
||||
fn from(c: BackendCoin) -> Self {
|
||||
Coin {
|
||||
amount: c.amount.to_string(),
|
||||
denom: Denom::from_str(c.denom.as_ref()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CosmosCoin> for Coin {
|
||||
fn from(c: CosmosCoin) -> Coin {
|
||||
Coin {
|
||||
amount: c.amount.to_string(),
|
||||
denom: Denom::from_str(c.denom.as_ref()).unwrap(),
|
||||
fn from(c: CosmosCoin) -> Coin {
|
||||
Coin {
|
||||
amount: c.amount.to_string(),
|
||||
denom: Denom::from_str(c.denom.as_ref()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CosmWasmCoin> for Coin {
|
||||
fn from(c: CosmWasmCoin) -> Coin {
|
||||
Coin {
|
||||
amount: c.amount.to_string(),
|
||||
denom: Denom::from_str(&c.denom).unwrap(),
|
||||
fn from(c: CosmWasmCoin) -> Coin {
|
||||
Coin {
|
||||
amount: c.amount.to_string(),
|
||||
denom: Denom::from_str(&c.denom).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::error::BackendError;
|
||||
use serde_json::json;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use super::*;
|
||||
use crate::error::BackendError;
|
||||
use serde_json::json;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn json_to_coin() {
|
||||
let minor = json!({
|
||||
"amount": "1",
|
||||
"denom": "Minor"
|
||||
});
|
||||
#[test]
|
||||
fn json_to_coin() {
|
||||
let minor = json!({
|
||||
"amount": "1",
|
||||
"denom": "Minor"
|
||||
});
|
||||
|
||||
let major = json!({
|
||||
"amount": "1",
|
||||
"denom": "Major"
|
||||
});
|
||||
let major = json!({
|
||||
"amount": "1",
|
||||
"denom": "Major"
|
||||
});
|
||||
|
||||
let test_minor_coin = Coin::minor("1");
|
||||
let test_major_coin = Coin::major("1");
|
||||
let test_minor_coin = Coin::minor("1");
|
||||
let test_major_coin = Coin::major("1");
|
||||
|
||||
let minor_coin = serde_json::from_value::<Coin>(minor).unwrap();
|
||||
let major_coin = serde_json::from_value::<Coin>(major).unwrap();
|
||||
let minor_coin = serde_json::from_value::<Coin>(minor).unwrap();
|
||||
let major_coin = serde_json::from_value::<Coin>(major).unwrap();
|
||||
|
||||
assert_eq!(minor_coin, test_minor_coin);
|
||||
assert_eq!(major_coin, test_major_coin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_from_str() {
|
||||
assert_eq!(Denom::from_str("unym").unwrap(), Denom::Minor);
|
||||
assert_eq!(Denom::from_str("nym").unwrap(), Denom::Major);
|
||||
assert_eq!(Denom::from_str("minor").unwrap(), Denom::Minor);
|
||||
assert_eq!(Denom::from_str("major").unwrap(), Denom::Major);
|
||||
|
||||
assert!(matches!(
|
||||
Denom::from_str("foo").unwrap_err(),
|
||||
BackendError::InvalidDenom { .. },
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_conversions() {
|
||||
let minor = Coin::minor("1");
|
||||
let major = minor.to_major();
|
||||
|
||||
assert_eq!(major, Coin::major("0.000001"));
|
||||
|
||||
let minor = major.to_minor();
|
||||
assert_eq!(minor, Coin::minor("1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_denom_is_assumed_to_be_in_minor_denom() {
|
||||
let network_denom = "nym";
|
||||
assert!(matches!(
|
||||
Coin::minor("42")
|
||||
.denom_as_string(network_denom)
|
||||
.unwrap_err(),
|
||||
BackendError::InvalidNetworkDenom { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_denom_to_interpreted_using_network_denom() {
|
||||
let network_denom = "unym";
|
||||
assert_eq!(
|
||||
Coin::minor("42").denom_as_string(network_denom).unwrap(),
|
||||
"unym",
|
||||
);
|
||||
assert_eq!(
|
||||
Coin::major("42").denom_as_string(network_denom).unwrap(),
|
||||
"nym",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coin_to_coin_minor() {
|
||||
let network_denom = "unym";
|
||||
let coin = Coin::minor("42");
|
||||
|
||||
let backend_coin = coin.clone().into_backend_coin(&network_denom).unwrap();
|
||||
assert_eq!(backend_coin, BackendCoin::new(42, "unym"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coin_to_coin_major() {
|
||||
let network_denom = "unym";
|
||||
let coin = Coin::major("52");
|
||||
|
||||
let backend_coin = coin.clone().into_backend_coin(network_denom).unwrap();
|
||||
assert_eq!(backend_coin, BackendCoin::new(52, "nym"));
|
||||
}
|
||||
|
||||
fn amounts() -> Vec<&'static str> {
|
||||
vec![
|
||||
"1",
|
||||
"10",
|
||||
"100",
|
||||
"1000",
|
||||
"10000",
|
||||
"100000",
|
||||
"10000000",
|
||||
"100000000",
|
||||
"1000000000",
|
||||
"10000000000",
|
||||
"100000000000",
|
||||
"1000000000000",
|
||||
"10000000000000",
|
||||
"100000000000000",
|
||||
"1000000000000000",
|
||||
"10000000000000000",
|
||||
"100000000000000000",
|
||||
"1000000000000000000",
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coin_to_backend() {
|
||||
let network_denom = "unym";
|
||||
for amount in amounts() {
|
||||
let coin = Coin::minor(amount);
|
||||
let backend_coin = coin.into_backend_coin(network_denom).unwrap();
|
||||
assert_eq!(
|
||||
backend_coin,
|
||||
BackendCoin::new(amount.parse::<u128>().unwrap(), "unym")
|
||||
);
|
||||
assert_eq!(Coin::try_from(backend_coin).unwrap(), Coin::minor(amount));
|
||||
|
||||
let coin = Coin::major(amount);
|
||||
let backend_coin = coin.into_backend_coin(network_denom).unwrap();
|
||||
assert_eq!(
|
||||
backend_coin,
|
||||
BackendCoin::new(amount.parse::<u128>().unwrap(), "nym")
|
||||
);
|
||||
assert_eq!(Coin::try_from(backend_coin).unwrap(), Coin::major(amount));
|
||||
assert_eq!(minor_coin, test_minor_coin);
|
||||
assert_eq!(major_coin, test_major_coin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_from_str() {
|
||||
assert_eq!(Denom::from_str("unym").unwrap(), Denom::Minor);
|
||||
assert_eq!(Denom::from_str("nym").unwrap(), Denom::Major);
|
||||
assert_eq!(Denom::from_str("minor").unwrap(), Denom::Minor);
|
||||
assert_eq!(Denom::from_str("major").unwrap(), Denom::Major);
|
||||
|
||||
assert!(matches!(
|
||||
Denom::from_str("foo").unwrap_err(),
|
||||
BackendError::InvalidDenom { .. },
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_conversions() {
|
||||
let minor = Coin::minor("1");
|
||||
let major = minor.to_major();
|
||||
|
||||
assert_eq!(major, Coin::major("0.000001"));
|
||||
|
||||
let minor = major.to_minor();
|
||||
assert_eq!(minor, Coin::minor("1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_denom_is_assumed_to_be_in_minor_denom() {
|
||||
let network_denom = "nym";
|
||||
assert!(matches!(
|
||||
Coin::minor("42")
|
||||
.denom_as_string(network_denom)
|
||||
.unwrap_err(),
|
||||
BackendError::InvalidNetworkDenom { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_denom_to_interpreted_using_network_denom() {
|
||||
let network_denom = "unym";
|
||||
assert_eq!(
|
||||
Coin::minor("42").denom_as_string(network_denom).unwrap(),
|
||||
"unym",
|
||||
);
|
||||
assert_eq!(
|
||||
Coin::major("42").denom_as_string(network_denom).unwrap(),
|
||||
"nym",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coin_to_coin_minor() {
|
||||
let network_denom = "unym";
|
||||
let coin = Coin::minor("42");
|
||||
|
||||
let backend_coin = coin.clone().into_backend_coin(&network_denom).unwrap();
|
||||
assert_eq!(backend_coin, BackendCoin::new(42, "unym"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coin_to_coin_major() {
|
||||
let network_denom = "unym";
|
||||
let coin = Coin::major("52");
|
||||
|
||||
let backend_coin = coin.clone().into_backend_coin(network_denom).unwrap();
|
||||
assert_eq!(backend_coin, BackendCoin::new(52, "nym"));
|
||||
}
|
||||
|
||||
fn amounts() -> Vec<&'static str> {
|
||||
vec![
|
||||
"1",
|
||||
"10",
|
||||
"100",
|
||||
"1000",
|
||||
"10000",
|
||||
"100000",
|
||||
"10000000",
|
||||
"100000000",
|
||||
"1000000000",
|
||||
"10000000000",
|
||||
"100000000000",
|
||||
"1000000000000",
|
||||
"10000000000000",
|
||||
"100000000000000",
|
||||
"1000000000000000",
|
||||
"10000000000000000",
|
||||
"100000000000000000",
|
||||
"1000000000000000000",
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coin_to_backend() {
|
||||
let network_denom = "unym";
|
||||
for amount in amounts() {
|
||||
let coin = Coin::minor(amount);
|
||||
let backend_coin = coin.into_backend_coin(network_denom).unwrap();
|
||||
assert_eq!(
|
||||
backend_coin,
|
||||
BackendCoin::new(amount.parse::<u128>().unwrap(), "unym")
|
||||
);
|
||||
assert_eq!(Coin::try_from(backend_coin).unwrap(), Coin::minor(amount));
|
||||
|
||||
let coin = Coin::major(amount);
|
||||
let backend_coin = coin.into_backend_coin(network_denom).unwrap();
|
||||
assert_eq!(
|
||||
backend_coin,
|
||||
BackendCoin::new(amount.parse::<u128>().unwrap(), "nym")
|
||||
);
|
||||
assert_eq!(Coin::try_from(backend_coin).unwrap(), Coin::major(amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use url::Url;
|
||||
use validator_client::nymd::AccountId as CosmosAccountId;
|
||||
|
||||
pub const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str =
|
||||
"https://nymtech.net/.wellknown/wallet/validators.json";
|
||||
"https://nymtech.net/.wellknown/wallet/validators.json";
|
||||
|
||||
const CURRENT_GLOBAL_CONFIG_VERSION: u32 = 1;
|
||||
const CURRENT_NETWORK_CONFIG_VERSION: u32 = 1;
|
||||
@@ -26,448 +26,445 @@ pub(crate) const CUSTOM_SIMULATED_GAS_MULTIPLIER: f32 = 1.4;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct Config {
|
||||
// Base configuration is not part of the configuration file as it's not intended to be changed.
|
||||
base: Base,
|
||||
// Base configuration is not part of the configuration file as it's not intended to be changed.
|
||||
base: Base,
|
||||
|
||||
// Global configuration file
|
||||
global: Option<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]]
|
||||
@@ -482,17 +479,17 @@ api_url = 'https://bar/api'
|
||||
nymd_url = 'https://baz/'
|
||||
api_url = 'https://baz/api'
|
||||
"#
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_to_json() {
|
||||
let config = test_config();
|
||||
let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()];
|
||||
println!("{}", serde_json::to_string_pretty(netconfig).unwrap());
|
||||
assert_eq!(
|
||||
serde_json::to_string_pretty(netconfig).unwrap(),
|
||||
r#"{
|
||||
#[test]
|
||||
fn serialize_to_json() {
|
||||
let config = test_config();
|
||||
let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()];
|
||||
println!("{}", serde_json::to_string_pretty(netconfig).unwrap());
|
||||
assert_eq!(
|
||||
serde_json::to_string_pretty(netconfig).unwrap(),
|
||||
r#"{
|
||||
"version": 1,
|
||||
"selected_nymd_url": null,
|
||||
"selected_api_url": "https://my_api_url.com/",
|
||||
@@ -514,53 +511,53 @@ api_url = 'https://baz/api'
|
||||
}
|
||||
]
|
||||
}"#
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_and_deserialize_to_toml() {
|
||||
let config = test_config();
|
||||
let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()];
|
||||
let config_str = toml::to_string_pretty(netconfig).unwrap();
|
||||
let config_from_toml: NetworkConfig = toml::from_str(&config_str).unwrap();
|
||||
assert_eq!(netconfig, &config_from_toml);
|
||||
}
|
||||
#[test]
|
||||
fn serialize_and_deserialize_to_toml() {
|
||||
let config = test_config();
|
||||
let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()];
|
||||
let config_str = toml::to_string_pretty(netconfig).unwrap();
|
||||
let config_from_toml: NetworkConfig = toml::from_str(&config_str).unwrap();
|
||||
assert_eq!(netconfig, &config_from_toml);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_urls_parsed_from_config() {
|
||||
let config = test_config();
|
||||
#[test]
|
||||
fn get_urls_parsed_from_config() {
|
||||
let config = test_config();
|
||||
|
||||
let nymd_url = config
|
||||
.get_configured_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.map(|v| v.nymd_url)
|
||||
.unwrap();
|
||||
assert_eq!(nymd_url.as_ref(), "https://foo/");
|
||||
let nymd_url = config
|
||||
.get_configured_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.map(|v| v.nymd_url)
|
||||
.unwrap();
|
||||
assert_eq!(nymd_url.as_ref(), "https://foo/");
|
||||
|
||||
// The first entry is missing an API URL
|
||||
let api_url = config
|
||||
.get_configured_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.and_then(|v| v.api_url);
|
||||
assert_eq!(api_url, None);
|
||||
}
|
||||
// The first entry is missing an API URL
|
||||
let api_url = config
|
||||
.get_configured_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.and_then(|v| v.api_url);
|
||||
assert_eq!(api_url, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_urls_from_defaults() {
|
||||
let config = Config::default();
|
||||
#[test]
|
||||
fn get_urls_from_defaults() {
|
||||
let config = Config::default();
|
||||
|
||||
let nymd_url = config
|
||||
.get_base_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.map(|v| v.nymd_url)
|
||||
.unwrap();
|
||||
assert_eq!(nymd_url.as_ref(), "https://rpc.nyx.nodes.guru/");
|
||||
let nymd_url = config
|
||||
.get_base_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.map(|v| v.nymd_url)
|
||||
.unwrap();
|
||||
assert_eq!(nymd_url.as_ref(), "https://rpc.nyx.nodes.guru/");
|
||||
|
||||
let api_url = config
|
||||
.get_base_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.and_then(|v| v.api_url)
|
||||
.unwrap();
|
||||
assert_eq!(api_url.as_ref(), "https://validator.nymtech.net/api/",);
|
||||
}
|
||||
let api_url = config
|
||||
.get_base_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.and_then(|v| v.api_url)
|
||||
.unwrap();
|
||||
assert_eq!(api_url.as_ref(), "https://validator.nymtech.net/api/",);
|
||||
}
|
||||
}
|
||||
|
||||
+109
-109
@@ -7,120 +7,120 @@ use validator_client::{nymd::error::NymdError, ValidatorClientError};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BackendError {
|
||||
#[error("{source}")]
|
||||
Bip39Error {
|
||||
#[from]
|
||||
source: bip39::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
TendermintError {
|
||||
#[from]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
NymdError {
|
||||
#[from]
|
||||
source: NymdError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
CosmwasmStd {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ErrorReport {
|
||||
#[from]
|
||||
source: eyre::Report,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ValidatorApiError {
|
||||
#[from]
|
||||
source: ValidatorAPIError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
KeyDerivationError {
|
||||
#[from]
|
||||
source: argon2::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
IOError {
|
||||
#[from]
|
||||
source: io::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
MalformedUrlProvided {
|
||||
#[from]
|
||||
source: url::ParseError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("failed to encrypt the given data with the provided password")]
|
||||
EncryptionError,
|
||||
#[error("failed to decrypt the given data with the provided password")]
|
||||
DecryptionError,
|
||||
#[error("Client has not been initialized yet, connect with mnemonic to initialize")]
|
||||
ClientNotInitialized,
|
||||
#[error("No balance available for address {0}")]
|
||||
NoBalance(String),
|
||||
#[error("{0} is not a valid denomination string")]
|
||||
InvalidDenom(String),
|
||||
#[error("{0} is not a valid network denomination string")]
|
||||
InvalidNetworkDenom(String),
|
||||
#[error("The provided network is not supported (yet)")]
|
||||
NetworkNotSupported(config::defaults::all::Network),
|
||||
#[error("Could not access the local data storage directory")]
|
||||
UnknownStorageDirectory,
|
||||
#[error("No validator API URL configured")]
|
||||
NoValidatorApiUrlConfigured,
|
||||
#[error("The wallet file already exists")]
|
||||
WalletFileAlreadyExists,
|
||||
#[error("The wallet file is not found")]
|
||||
WalletFileNotFound,
|
||||
#[error("Login ID not found in wallet")]
|
||||
WalletNoSuchLoginId,
|
||||
#[error("Account ID not found in wallet login")]
|
||||
WalletNoSuchAccountIdInWalletLogin,
|
||||
#[error("Login ID already found in wallet")]
|
||||
WalletLoginIdAlreadyExists,
|
||||
#[error("Account ID already found in wallet login")]
|
||||
WalletAccountIdAlreadyExistsInWalletLogin,
|
||||
#[error("Mnemonic already found in wallet login, was it already imported?")]
|
||||
WalletMnemonicAlreadyExistsInWalletLogin,
|
||||
#[error("Adding a different password to the wallet not currently supported")]
|
||||
WalletDifferentPasswordDetected,
|
||||
#[error("Unexpted mnemonic account for login")]
|
||||
WalletUnexpectedMnemonicAccount,
|
||||
#[error("Unexpted multiple account entry for login")]
|
||||
WalletUnexpectedMultipleAccounts,
|
||||
#[error("Failed to derive address from mnemonic")]
|
||||
FailedToDeriveAddress,
|
||||
#[error("{0}")]
|
||||
ValueParseError(#[from] ParseIntError),
|
||||
#[error("{source}")]
|
||||
Bip39Error {
|
||||
#[from]
|
||||
source: bip39::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
TendermintError {
|
||||
#[from]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
NymdError {
|
||||
#[from]
|
||||
source: NymdError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
CosmwasmStd {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ErrorReport {
|
||||
#[from]
|
||||
source: eyre::Report,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ValidatorApiError {
|
||||
#[from]
|
||||
source: ValidatorAPIError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
KeyDerivationError {
|
||||
#[from]
|
||||
source: argon2::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
IOError {
|
||||
#[from]
|
||||
source: io::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
MalformedUrlProvided {
|
||||
#[from]
|
||||
source: url::ParseError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("failed to encrypt the given data with the provided password")]
|
||||
EncryptionError,
|
||||
#[error("failed to decrypt the given data with the provided password")]
|
||||
DecryptionError,
|
||||
#[error("Client has not been initialized yet, connect with mnemonic to initialize")]
|
||||
ClientNotInitialized,
|
||||
#[error("No balance available for address {0}")]
|
||||
NoBalance(String),
|
||||
#[error("{0} is not a valid denomination string")]
|
||||
InvalidDenom(String),
|
||||
#[error("{0} is not a valid network denomination string")]
|
||||
InvalidNetworkDenom(String),
|
||||
#[error("The provided network is not supported (yet)")]
|
||||
NetworkNotSupported(config::defaults::all::Network),
|
||||
#[error("Could not access the local data storage directory")]
|
||||
UnknownStorageDirectory,
|
||||
#[error("No validator API URL configured")]
|
||||
NoValidatorApiUrlConfigured,
|
||||
#[error("The wallet file already exists")]
|
||||
WalletFileAlreadyExists,
|
||||
#[error("The wallet file is not found")]
|
||||
WalletFileNotFound,
|
||||
#[error("Login ID not found in wallet")]
|
||||
WalletNoSuchLoginId,
|
||||
#[error("Account ID not found in wallet login")]
|
||||
WalletNoSuchAccountIdInWalletLogin,
|
||||
#[error("Login ID already found in wallet")]
|
||||
WalletLoginIdAlreadyExists,
|
||||
#[error("Account ID already found in wallet login")]
|
||||
WalletAccountIdAlreadyExistsInWalletLogin,
|
||||
#[error("Mnemonic already found in wallet login, was it already imported?")]
|
||||
WalletMnemonicAlreadyExistsInWalletLogin,
|
||||
#[error("Adding a different password to the wallet not currently supported")]
|
||||
WalletDifferentPasswordDetected,
|
||||
#[error("Unexpted mnemonic account for login")]
|
||||
WalletUnexpectedMnemonicAccount,
|
||||
#[error("Unexpted multiple account entry for login")]
|
||||
WalletUnexpectedMultipleAccounts,
|
||||
#[error("Failed to derive address from mnemonic")]
|
||||
FailedToDeriveAddress,
|
||||
#[error("{0}")]
|
||||
ValueParseError(#[from] ParseIntError),
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+121
-121
@@ -1,6 +1,6 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use crate::menu::AddDefaultSubmenus;
|
||||
@@ -24,128 +24,128 @@ mod utils;
|
||||
mod wallet_storage;
|
||||
|
||||
fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::default())))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
mixnet::account::add_account_for_password,
|
||||
mixnet::account::connect_with_mnemonic,
|
||||
mixnet::account::create_new_account,
|
||||
mixnet::account::create_new_mnemonic,
|
||||
mixnet::account::create_password,
|
||||
mixnet::account::does_password_file_exist,
|
||||
mixnet::account::get_balance,
|
||||
mixnet::account::list_accounts,
|
||||
mixnet::account::logout,
|
||||
mixnet::account::remove_account_for_password,
|
||||
mixnet::account::remove_password,
|
||||
mixnet::account::show_mnemonic_for_account_in_password,
|
||||
mixnet::account::sign_in_with_password,
|
||||
mixnet::account::sign_in_with_password_and_account_id,
|
||||
mixnet::account::switch_network,
|
||||
mixnet::account::validate_mnemonic,
|
||||
mixnet::admin::get_contract_settings,
|
||||
mixnet::admin::update_contract_settings,
|
||||
mixnet::bond::bond_gateway,
|
||||
mixnet::bond::bond_mixnode,
|
||||
mixnet::bond::gateway_bond_details,
|
||||
mixnet::bond::get_operator_rewards,
|
||||
mixnet::bond::mixnode_bond_details,
|
||||
mixnet::bond::unbond_gateway,
|
||||
mixnet::bond::unbond_mixnode,
|
||||
mixnet::bond::update_mixnode,
|
||||
mixnet::delegate::delegate_to_mixnode,
|
||||
mixnet::delegate::get_delegator_rewards,
|
||||
mixnet::delegate::get_pending_delegation_events,
|
||||
mixnet::delegate::get_reverse_mix_delegations_paged,
|
||||
mixnet::delegate::undelegate_from_mixnode,
|
||||
mixnet::epoch::get_current_epoch,
|
||||
mixnet::send::send,
|
||||
network_config::add_validator,
|
||||
network_config::get_validator_api_urls,
|
||||
network_config::get_validator_nymd_urls,
|
||||
network_config::remove_validator,
|
||||
network_config::select_validator_api_url,
|
||||
network_config::select_validator_nymd_url,
|
||||
network_config::update_validator_urls,
|
||||
state::load_config_from_files,
|
||||
state::save_config_to_files,
|
||||
utils::major_to_minor,
|
||||
utils::minor_to_major,
|
||||
utils::owns_gateway,
|
||||
utils::owns_mixnode,
|
||||
utils::get_env,
|
||||
validator_api::status::gateway_core_node_status,
|
||||
validator_api::status::mixnode_core_node_status,
|
||||
validator_api::status::mixnode_inclusion_probability,
|
||||
validator_api::status::mixnode_reward_estimation,
|
||||
validator_api::status::mixnode_stake_saturation,
|
||||
validator_api::status::mixnode_status,
|
||||
vesting::bond::vesting_bond_gateway,
|
||||
vesting::bond::vesting_bond_mixnode,
|
||||
vesting::bond::vesting_unbond_gateway,
|
||||
vesting::bond::vesting_unbond_mixnode,
|
||||
vesting::bond::vesting_update_mixnode,
|
||||
vesting::bond::withdraw_vested_coins,
|
||||
vesting::delegate::get_pending_vesting_delegation_events,
|
||||
vesting::delegate::vesting_delegate_to_mixnode,
|
||||
vesting::delegate::vesting_undelegate_from_mixnode,
|
||||
vesting::queries::delegated_free,
|
||||
vesting::queries::delegated_vesting,
|
||||
vesting::queries::get_account_info,
|
||||
vesting::queries::get_current_vesting_period,
|
||||
vesting::queries::locked_coins,
|
||||
vesting::queries::original_vesting,
|
||||
vesting::queries::spendable_coins,
|
||||
vesting::queries::vested_coins,
|
||||
vesting::queries::vesting_coins,
|
||||
vesting::queries::vesting_end_time,
|
||||
vesting::queries::vesting_get_gateway_pledge,
|
||||
vesting::queries::vesting_get_mixnode_pledge,
|
||||
vesting::queries::vesting_start_time,
|
||||
// simulate endpoints
|
||||
simulate::admin::simulate_update_contract_settings,
|
||||
simulate::mixnet::simulate_bond_gateway,
|
||||
simulate::mixnet::simulate_bond_mixnode,
|
||||
simulate::mixnet::simulate_unbond_gateway,
|
||||
simulate::mixnet::simulate_unbond_mixnode,
|
||||
simulate::mixnet::simulate_update_mixnode,
|
||||
simulate::mixnet::simulate_delegate_to_mixnode,
|
||||
simulate::mixnet::simulate_undelegate_from_mixnode,
|
||||
simulate::cosmos::simulate_send,
|
||||
simulate::vesting::simulate_vesting_bond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
simulate::vesting::simulate_vesting_unbond_gateway,
|
||||
simulate::vesting::simulate_vesting_unbond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_mixnode,
|
||||
simulate::vesting::simulate_withdraw_vested_coins,
|
||||
])
|
||||
.menu(Menu::new().add_default_app_submenu_if_macos())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::default())))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
mixnet::account::add_account_for_password,
|
||||
mixnet::account::connect_with_mnemonic,
|
||||
mixnet::account::create_new_account,
|
||||
mixnet::account::create_new_mnemonic,
|
||||
mixnet::account::create_password,
|
||||
mixnet::account::does_password_file_exist,
|
||||
mixnet::account::get_balance,
|
||||
mixnet::account::list_accounts,
|
||||
mixnet::account::logout,
|
||||
mixnet::account::remove_account_for_password,
|
||||
mixnet::account::remove_password,
|
||||
mixnet::account::show_mnemonic_for_account_in_password,
|
||||
mixnet::account::sign_in_with_password,
|
||||
mixnet::account::sign_in_with_password_and_account_id,
|
||||
mixnet::account::switch_network,
|
||||
mixnet::account::validate_mnemonic,
|
||||
mixnet::admin::get_contract_settings,
|
||||
mixnet::admin::update_contract_settings,
|
||||
mixnet::bond::bond_gateway,
|
||||
mixnet::bond::bond_mixnode,
|
||||
mixnet::bond::gateway_bond_details,
|
||||
mixnet::bond::get_operator_rewards,
|
||||
mixnet::bond::mixnode_bond_details,
|
||||
mixnet::bond::unbond_gateway,
|
||||
mixnet::bond::unbond_mixnode,
|
||||
mixnet::bond::update_mixnode,
|
||||
mixnet::delegate::delegate_to_mixnode,
|
||||
mixnet::delegate::get_delegator_rewards,
|
||||
mixnet::delegate::get_pending_delegation_events,
|
||||
mixnet::delegate::get_reverse_mix_delegations_paged,
|
||||
mixnet::delegate::undelegate_from_mixnode,
|
||||
mixnet::epoch::get_current_epoch,
|
||||
mixnet::send::send,
|
||||
network_config::add_validator,
|
||||
network_config::get_validator_api_urls,
|
||||
network_config::get_validator_nymd_urls,
|
||||
network_config::remove_validator,
|
||||
network_config::select_validator_api_url,
|
||||
network_config::select_validator_nymd_url,
|
||||
network_config::update_validator_urls,
|
||||
state::load_config_from_files,
|
||||
state::save_config_to_files,
|
||||
utils::major_to_minor,
|
||||
utils::minor_to_major,
|
||||
utils::owns_gateway,
|
||||
utils::owns_mixnode,
|
||||
utils::get_env,
|
||||
validator_api::status::gateway_core_node_status,
|
||||
validator_api::status::mixnode_core_node_status,
|
||||
validator_api::status::mixnode_inclusion_probability,
|
||||
validator_api::status::mixnode_reward_estimation,
|
||||
validator_api::status::mixnode_stake_saturation,
|
||||
validator_api::status::mixnode_status,
|
||||
vesting::bond::vesting_bond_gateway,
|
||||
vesting::bond::vesting_bond_mixnode,
|
||||
vesting::bond::vesting_unbond_gateway,
|
||||
vesting::bond::vesting_unbond_mixnode,
|
||||
vesting::bond::vesting_update_mixnode,
|
||||
vesting::bond::withdraw_vested_coins,
|
||||
vesting::delegate::get_pending_vesting_delegation_events,
|
||||
vesting::delegate::vesting_delegate_to_mixnode,
|
||||
vesting::delegate::vesting_undelegate_from_mixnode,
|
||||
vesting::queries::delegated_free,
|
||||
vesting::queries::delegated_vesting,
|
||||
vesting::queries::get_account_info,
|
||||
vesting::queries::get_current_vesting_period,
|
||||
vesting::queries::locked_coins,
|
||||
vesting::queries::original_vesting,
|
||||
vesting::queries::spendable_coins,
|
||||
vesting::queries::vested_coins,
|
||||
vesting::queries::vesting_coins,
|
||||
vesting::queries::vesting_end_time,
|
||||
vesting::queries::vesting_get_gateway_pledge,
|
||||
vesting::queries::vesting_get_mixnode_pledge,
|
||||
vesting::queries::vesting_start_time,
|
||||
// simulate endpoints
|
||||
simulate::admin::simulate_update_contract_settings,
|
||||
simulate::mixnet::simulate_bond_gateway,
|
||||
simulate::mixnet::simulate_bond_mixnode,
|
||||
simulate::mixnet::simulate_unbond_gateway,
|
||||
simulate::mixnet::simulate_unbond_mixnode,
|
||||
simulate::mixnet::simulate_update_mixnode,
|
||||
simulate::mixnet::simulate_delegate_to_mixnode,
|
||||
simulate::mixnet::simulate_undelegate_from_mixnode,
|
||||
simulate::cosmos::simulate_send,
|
||||
simulate::vesting::simulate_vesting_bond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
simulate::vesting::simulate_vesting_unbond_gateway,
|
||||
simulate::vesting::simulate_vesting_unbond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_mixnode,
|
||||
simulate::vesting::simulate_withdraw_vested_coins,
|
||||
])
|
||||
.menu(Menu::new().add_default_app_submenu_if_macos())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("rustls", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_util", log::LevelFilter::Warn)
|
||||
.init();
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("rustls", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_util", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,54 +12,54 @@ use strum::EnumIter;
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/network.ts"))]
|
||||
#[derive(Copy, Clone, Debug, Deserialize, EnumIter, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum Network {
|
||||
QA,
|
||||
SANDBOX,
|
||||
MAINNET,
|
||||
QA,
|
||||
SANDBOX,
|
||||
MAINNET,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn as_key(&self) -> String {
|
||||
self.to_string().to_lowercase()
|
||||
}
|
||||
|
||||
pub fn denom(&self) -> &str {
|
||||
match self {
|
||||
// network defaults should be correctly formatted
|
||||
Network::QA => qa::DENOM,
|
||||
Network::SANDBOX => sandbox::DENOM,
|
||||
Network::MAINNET => mainnet::DENOM,
|
||||
pub fn as_key(&self) -> String {
|
||||
self.to_string().to_lowercase()
|
||||
}
|
||||
|
||||
pub fn denom(&self) -> &str {
|
||||
match self {
|
||||
// network defaults should be correctly formatted
|
||||
Network::QA => qa::DENOM,
|
||||
Network::SANDBOX => sandbox::DENOM,
|
||||
Network::MAINNET => mainnet::DENOM,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
fn default() -> Self {
|
||||
Network::MAINNET
|
||||
}
|
||||
fn default() -> Self {
|
||||
Network::MAINNET
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Network {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigNetwork> for Network {
|
||||
fn from(network: ConfigNetwork) -> Self {
|
||||
match network {
|
||||
ConfigNetwork::QA => Network::QA,
|
||||
ConfigNetwork::SANDBOX => Network::SANDBOX,
|
||||
ConfigNetwork::MAINNET => Network::MAINNET,
|
||||
fn from(network: ConfigNetwork) -> Self {
|
||||
match network {
|
||||
ConfigNetwork::QA => Network::QA,
|
||||
ConfigNetwork::SANDBOX => Network::SANDBOX,
|
||||
ConfigNetwork::MAINNET => Network::MAINNET,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Network> for ConfigNetwork {
|
||||
fn from(network: Network) -> Self {
|
||||
match network {
|
||||
Network::QA => ConfigNetwork::QA,
|
||||
Network::SANDBOX => ConfigNetwork::SANDBOX,
|
||||
Network::MAINNET => ConfigNetwork::MAINNET,
|
||||
fn from(network: Network) -> Self {
|
||||
match network {
|
||||
Network::QA => ConfigNetwork::QA,
|
||||
Network::SANDBOX => ConfigNetwork::SANDBOX,
|
||||
Network::MAINNET => ConfigNetwork::MAINNET,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,15 @@ use tokio::sync::RwLock;
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ValidatorUrls {
|
||||
pub urls: Vec<ValidatorUrl>,
|
||||
pub urls: Vec<ValidatorUrl>,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurl.ts"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ValidatorUrl {
|
||||
pub url: String,
|
||||
pub name: Option<String>,
|
||||
pub url: String,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
// The type used when adding or removing validators, effectively the input.
|
||||
@@ -31,98 +31,98 @@ pub struct ValidatorUrl {
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Validator {
|
||||
pub nymd_url: String,
|
||||
pub nymd_name: Option<String>,
|
||||
pub api_url: Option<String>,
|
||||
pub nymd_url: String,
|
||||
pub nymd_name: Option<String>,
|
||||
pub api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Validator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let nymd_url = format!("nymd_url: {}", self.nymd_url);
|
||||
let api_url = self
|
||||
.api_url
|
||||
.as_ref()
|
||||
.map(|api_url| format!(", api_url: {}", api_url))
|
||||
.unwrap_or_default();
|
||||
write!(f, "{nymd_url}{api_url}")
|
||||
}
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let nymd_url = format!("nymd_url: {}", self.nymd_url);
|
||||
let api_url = self
|
||||
.api_url
|
||||
.as_ref()
|
||||
.map(|api_url| format!(", api_url: {}", api_url))
|
||||
.unwrap_or_default();
|
||||
write!(f, "{nymd_url}{api_url}")
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_validator_nymd_urls(
|
||||
network: WalletNetwork,
|
||||
state: tauri::State<'_, Arc<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,52 +13,52 @@ use validator_client::nymd::Fee;
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/stateparams.ts"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TauriContractStateParams {
|
||||
minimum_mixnode_pledge: String,
|
||||
minimum_gateway_pledge: String,
|
||||
mixnode_rewarded_set_size: u32,
|
||||
mixnode_active_set_size: u32,
|
||||
minimum_mixnode_pledge: String,
|
||||
minimum_gateway_pledge: String,
|
||||
mixnode_rewarded_set_size: u32,
|
||||
mixnode_active_set_size: u32,
|
||||
}
|
||||
|
||||
impl From<ContractStateParams> for TauriContractStateParams {
|
||||
fn from(p: ContractStateParams) -> TauriContractStateParams {
|
||||
TauriContractStateParams {
|
||||
minimum_mixnode_pledge: p.minimum_mixnode_pledge.to_string(),
|
||||
minimum_gateway_pledge: p.minimum_gateway_pledge.to_string(),
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
fn from(p: ContractStateParams) -> TauriContractStateParams {
|
||||
TauriContractStateParams {
|
||||
minimum_mixnode_pledge: p.minimum_mixnode_pledge.to_string(),
|
||||
minimum_gateway_pledge: p.minimum_gateway_pledge.to_string(),
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TauriContractStateParams> for ContractStateParams {
|
||||
type Error = BackendError;
|
||||
type Error = BackendError;
|
||||
|
||||
fn try_from(p: TauriContractStateParams) -> Result<ContractStateParams, Self::Error> {
|
||||
Ok(ContractStateParams {
|
||||
minimum_mixnode_pledge: Uint128::try_from(p.minimum_mixnode_pledge.as_str())?,
|
||||
minimum_gateway_pledge: Uint128::try_from(p.minimum_gateway_pledge.as_str())?,
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
})
|
||||
}
|
||||
fn try_from(p: TauriContractStateParams) -> Result<ContractStateParams, Self::Error> {
|
||||
Ok(ContractStateParams {
|
||||
minimum_mixnode_pledge: Uint128::try_from(p.minimum_mixnode_pledge.as_str())?,
|
||||
minimum_gateway_pledge: Uint128::try_from(p.minimum_gateway_pledge.as_str())?,
|
||||
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
|
||||
mixnode_active_set_size: p.mixnode_active_set_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_contract_settings(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<TauriContractStateParams, BackendError> {
|
||||
Ok(nymd_client!(state).get_contract_settings().await?.into())
|
||||
Ok(nymd_client!(state).get_contract_settings().await?.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_contract_settings(
|
||||
params: TauriContractStateParams,
|
||||
fee: Option<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()?;
|
||||
nymd_client!(state)
|
||||
.update_contract_settings(mixnet_contract_settings_params.clone(), fee)
|
||||
.await?;
|
||||
Ok(mixnet_contract_settings_params.into())
|
||||
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
|
||||
nymd_client!(state)
|
||||
.update_contract_settings(mixnet_contract_settings_params.clone(), fee)
|
||||
.await?;
|
||||
Ok(mixnet_contract_settings_params.into())
|
||||
}
|
||||
|
||||
@@ -11,88 +11,88 @@ use validator_client::nymd::Fee;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.bond_gateway(gateway, owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.bond_gateway(gateway, owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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<(), BackendError> {
|
||||
nymd_client!(state).unbond_gateway(fee).await?;
|
||||
Ok(())
|
||||
nymd_client!(state).unbond_gateway(fee).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state).unbond_mixnode(fee).await?;
|
||||
Ok(())
|
||||
nymd_client!(state).unbond_mixnode(fee).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.bond_mixnode(mixnode, owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.bond_mixnode(mixnode, owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state)
|
||||
.update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
nymd_client!(state)
|
||||
.update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<MixNodeBond>, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_mixnode(client.nymd.address()).await?;
|
||||
Ok(bond)
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_mixnode(client.nymd.address()).await?;
|
||||
Ok(bond)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn gateway_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<GatewayBond>, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_gateway(client.nymd.address()).await?;
|
||||
Ok(bond)
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_gateway(client.nymd.address()).await?;
|
||||
Ok(bond)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_operator_rewards(
|
||||
address: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Uint128, BackendError> {
|
||||
Ok(nymd_client!(state).get_operator_rewards(address).await?)
|
||||
Ok(nymd_client!(state).get_operator_rewards(address).await?)
|
||||
}
|
||||
|
||||
@@ -12,73 +12,67 @@ 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> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.get_pending_delegation_events(nymd_client!(state).address().to_string(), None)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.into())
|
||||
.collect::<Vec<DelegationEvent>>(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.get_pending_delegation_events(nymd_client!(state).address().to_string(), None)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.into())
|
||||
.collect::<Vec<DelegationEvent>>())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.delegate_to_mixnode(identity, delegation.clone(), fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
Some(delegation.into()),
|
||||
))
|
||||
let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.delegate_to_mixnode(identity, delegation.clone(), fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
Some(delegation.into()),
|
||||
))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
nymd_client!(state)
|
||||
.remove_mixnode_delegation(identity, fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
None,
|
||||
))
|
||||
nymd_client!(state)
|
||||
.remove_mixnode_delegation(identity, fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_reverse_mix_delegations_paged(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<PagedDelegatorDelegationsResponse, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.get_delegator_delegations_paged(nymd_client!(state).address().to_string(), None, None)
|
||||
.await?,
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.get_delegator_delegations_paged(nymd_client!(state).address().to_string(), None, None)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_delegator_rewards(
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Uint128, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.get_delegator_rewards(address, mix_identity, proxy)
|
||||
.await?,
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.get_delegator_rewards(address, mix_identity, proxy)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -10,27 +10,27 @@ use tokio::sync::RwLock;
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/epoch.ts"))]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct Epoch {
|
||||
id: u32,
|
||||
start: i64,
|
||||
end: i64,
|
||||
duration_seconds: u64,
|
||||
id: u32,
|
||||
start: i64,
|
||||
end: i64,
|
||||
duration_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<Interval> for Epoch {
|
||||
fn from(interval: Interval) -> Self {
|
||||
Self {
|
||||
id: interval.id(),
|
||||
start: interval.start_unix_timestamp(),
|
||||
end: interval.end_unix_timestamp(),
|
||||
duration_seconds: interval.length().as_secs(),
|
||||
fn from(interval: Interval) -> Self {
|
||||
Self {
|
||||
id: interval.id(),
|
||||
start: interval.start_unix_timestamp(),
|
||||
end: interval.end_unix_timestamp(),
|
||||
duration_seconds: interval.length().as_secs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_current_epoch(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Epoch, BackendError> {
|
||||
let interval = nymd_client!(state).get_current_epoch().await?;
|
||||
Ok(interval.into())
|
||||
let interval = nymd_client!(state).get_current_epoch().await?;
|
||||
Ok(interval.into())
|
||||
}
|
||||
|
||||
@@ -12,58 +12,58 @@ use validator_client::nymd::{AccountId, Fee, TxResponse};
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/tauritxresult.ts"))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TauriTxResult {
|
||||
block_height: u64,
|
||||
code: u32,
|
||||
details: TransactionDetails,
|
||||
gas_used: u64,
|
||||
gas_wanted: u64,
|
||||
tx_hash: String,
|
||||
block_height: u64,
|
||||
code: u32,
|
||||
details: TransactionDetails,
|
||||
gas_used: u64,
|
||||
gas_wanted: u64,
|
||||
tx_hash: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/transactiondetails.ts")
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/transactiondetails.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TransactionDetails {
|
||||
amount: Coin,
|
||||
from_address: String,
|
||||
to_address: String,
|
||||
amount: Coin,
|
||||
from_address: String,
|
||||
to_address: String,
|
||||
}
|
||||
|
||||
impl TauriTxResult {
|
||||
fn new(t: TxResponse, details: TransactionDetails) -> TauriTxResult {
|
||||
TauriTxResult {
|
||||
block_height: t.height.value(),
|
||||
code: t.tx_result.code.value(),
|
||||
details,
|
||||
gas_used: t.tx_result.gas_used.value(),
|
||||
gas_wanted: t.tx_result.gas_wanted.value(),
|
||||
tx_hash: t.hash.to_string(),
|
||||
fn new(t: TxResponse, details: TransactionDetails) -> TauriTxResult {
|
||||
TauriTxResult {
|
||||
block_height: t.height.value(),
|
||||
code: t.tx_result.code.value(),
|
||||
details,
|
||||
gas_used: t.tx_result.gas_used.value(),
|
||||
gas_wanted: t.tx_result.gas_wanted.value(),
|
||||
tx_hash: t.hash.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send(
|
||||
address: &str,
|
||||
amount: Coin,
|
||||
memo: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: &str,
|
||||
amount: Coin,
|
||||
memo: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<TauriTxResult, BackendError> {
|
||||
let address = AccountId::from_str(address)?;
|
||||
let amount = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
let result = nymd_client!(state)
|
||||
.send(&address, vec![amount.clone()], memo, fee)
|
||||
.await?;
|
||||
Ok(TauriTxResult::new(
|
||||
result,
|
||||
TransactionDetails {
|
||||
from_address: nymd_client!(state).address().to_string(),
|
||||
to_address: address.to_string(),
|
||||
amount: amount.into(),
|
||||
},
|
||||
))
|
||||
let address = AccountId::from_str(address)?;
|
||||
let amount = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
let result = nymd_client!(state)
|
||||
.send(&address, vec![amount.clone()], memo, fee)
|
||||
.await?;
|
||||
Ok(TauriTxResult::new(
|
||||
result,
|
||||
TransactionDetails {
|
||||
from_address: nymd_client!(state).address().to_string(),
|
||||
to_address: address.to_string(),
|
||||
amount: amount.into(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -11,21 +11,21 @@ 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_fundless_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::UpdateContractStateParams(mixnet_contract_settings_params),
|
||||
)?;
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::UpdateContractStateParams(mixnet_contract_settings_params),
|
||||
)?;
|
||||
|
||||
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: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: &str,
|
||||
amount: Coin,
|
||||
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 = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
let to_address = AccountId::from_str(address)?;
|
||||
let amount = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let from_address = client.nymd.address().clone();
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let client = guard.current_client()?;
|
||||
let from_address = client.nymd.address().clone();
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
|
||||
// TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code
|
||||
let msg = MsgSend {
|
||||
from_address,
|
||||
to_address,
|
||||
amount: vec![amount.into()],
|
||||
};
|
||||
// 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: vec![amount.into()],
|
||||
};
|
||||
|
||||
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,160 +11,160 @@ use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
|
||||
// TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
},
|
||||
vec![pledge],
|
||||
)?;
|
||||
// TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
},
|
||||
vec![pledge],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_unbond_gateway(
|
||||
state: tauri::State<'_, Arc<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_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondGateway {})?;
|
||||
let msg = client
|
||||
.nymd
|
||||
.wrap_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondGateway {})?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::BondMixnode {
|
||||
mix_node: mixnode,
|
||||
owner_signature,
|
||||
},
|
||||
vec![pledge],
|
||||
)?;
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::BondMixnode {
|
||||
mix_node: mixnode,
|
||||
owner_signature,
|
||||
},
|
||||
vec![pledge],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_unbond_mixnode(
|
||||
state: tauri::State<'_, Arc<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_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondMixnode {})?;
|
||||
let msg = client
|
||||
.nymd
|
||||
.wrap_fundless_contract_execute_message(mixnet_contract, &ExecuteMsg::UnbondMixnode {})?;
|
||||
|
||||
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_fundless_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
},
|
||||
)?;
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
},
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let delegation = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
let guard = state.read().await;
|
||||
let delegation = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::DelegateToMixnode {
|
||||
mix_identity: identity.to_string(),
|
||||
},
|
||||
vec![delegation],
|
||||
)?;
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::DelegateToMixnode {
|
||||
mix_identity: identity.to_string(),
|
||||
},
|
||||
vec![delegation],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<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_fundless_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::UndelegateFromMixnode {
|
||||
mix_identity: identity.to_string(),
|
||||
},
|
||||
)?;
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
mixnet_contract,
|
||||
&ExecuteMsg::UndelegateFromMixnode {
|
||||
mix_identity: identity.to_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -14,52 +14,50 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FeeDetails {
|
||||
// expected to be used by the wallet in order to display detailed fee information to the user
|
||||
pub amount: Option<Coin>,
|
||||
pub fee: Fee,
|
||||
// expected to be used by the wallet in order to display detailed fee information to the user
|
||||
pub amount: Option<Coin>,
|
||||
pub fee: Fee,
|
||||
}
|
||||
|
||||
impl SimulateResult {
|
||||
pub fn detailed_fee(&self) -> FeeDetails {
|
||||
FeeDetails {
|
||||
amount: self.to_fee_amount().map(Into::into),
|
||||
fee: self.to_fee(),
|
||||
pub fn detailed_fee(&self) -> FeeDetails {
|
||||
FeeDetails {
|
||||
amount: self.to_fee_amount().map(Into::into),
|
||||
fee: self.to_fee(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_fee_amount(&self) -> Option<CosmosCoin> {
|
||||
self
|
||||
.gas_info
|
||||
.map(|gas_info| &self.gas_price * gas_info.gas_used)
|
||||
}
|
||||
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()
|
||||
}
|
||||
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,135 +12,135 @@ use vesting_contract_common::ExecuteMsg;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
amount: pledge.into(),
|
||||
},
|
||||
)?;
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
amount: pledge.into(),
|
||||
},
|
||||
)?;
|
||||
|
||||
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_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondGateway {})?;
|
||||
let msg = client
|
||||
.nymd
|
||||
.wrap_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondGateway {})?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into_backend_coin(guard.current_network().denom())?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::BondMixnode {
|
||||
mix_node: mixnode,
|
||||
owner_signature,
|
||||
amount: pledge.into(),
|
||||
},
|
||||
)?;
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::BondMixnode {
|
||||
mix_node: mixnode,
|
||||
owner_signature,
|
||||
amount: pledge.into(),
|
||||
},
|
||||
)?;
|
||||
|
||||
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_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondMixnode {})?;
|
||||
let msg = client
|
||||
.nymd
|
||||
.wrap_fundless_contract_execute_message(vesting_contract, &ExecuteMsg::UnbondMixnode {})?;
|
||||
|
||||
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_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
},
|
||||
)?;
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
},
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(SimulateResult::new(result.gas_info, gas_price).detailed_fee())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
amount: Coin,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let amount = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
let guard = state.read().await;
|
||||
let amount = amount.into_backend_coin(guard.current_network().denom())?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: amount.into(),
|
||||
},
|
||||
)?;
|
||||
let msg = client.nymd.wrap_fundless_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::WithdrawVestedCoins {
|
||||
amount: amount.into(),
|
||||
},
|
||||
)?;
|
||||
|
||||
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?)
|
||||
}
|
||||
|
||||
@@ -9,73 +9,73 @@ use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
gateway: Gateway,
|
||||
pledge: Coin,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_bond_gateway(gateway, &owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_bond_gateway(gateway, &owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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<(), BackendError> {
|
||||
nymd_client!(state).vesting_unbond_gateway(fee).await?;
|
||||
Ok(())
|
||||
nymd_client!(state).vesting_unbond_gateway(fee).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state).vesting_unbond_mixnode(fee).await?;
|
||||
Ok(())
|
||||
nymd_client!(state).vesting_unbond_mixnode(fee).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_bond_mixnode(mixnode, &owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
let pledge = pledge.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_bond_mixnode(mixnode, &owner_signature, pledge, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
let amount = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.withdraw_vested_coins(amount, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
let amount = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.withdraw_vested_coins(amount, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
nymd_client!(state)
|
||||
.vesting_update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
nymd_client!(state)
|
||||
.vesting_update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,55 +10,53 @@ use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
|
||||
#[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> {
|
||||
let guard = state.read().await;
|
||||
let client = &guard.current_client()?.nymd;
|
||||
let vesting_contract = client.vesting_contract_address()?;
|
||||
let guard = state.read().await;
|
||||
let client = &guard.current_client()?.nymd;
|
||||
let vesting_contract = client.vesting_contract_address()?;
|
||||
|
||||
Ok(
|
||||
client
|
||||
.get_pending_delegation_events(
|
||||
client.address().to_string(),
|
||||
Some(vesting_contract.to_string()),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.into())
|
||||
.collect::<Vec<DelegationEvent>>(),
|
||||
)
|
||||
Ok(client
|
||||
.get_pending_delegation_events(
|
||||
client.address().to_string(),
|
||||
Some(vesting_contract.to_string()),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.into())
|
||||
.collect::<Vec<DelegationEvent>>())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
identity: &str,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_delegate_to_mixnode(identity, delegation.clone(), fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
Some(delegation.into()),
|
||||
))
|
||||
let delegation = amount.into_backend_coin(state.read().await.current_network().denom())?;
|
||||
nymd_client!(state)
|
||||
.vesting_delegate_to_mixnode(identity, delegation.clone(), fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
Some(delegation.into()),
|
||||
))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<DelegationResult, BackendError> {
|
||||
nymd_client!(state)
|
||||
.vesting_undelegate_from_mixnode(identity, fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
None,
|
||||
))
|
||||
nymd_client!(state)
|
||||
.vesting_undelegate_from_mixnode(identity, fee)
|
||||
.await?;
|
||||
Ok(DelegationResult::new(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
identity,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -13,90 +13,90 @@ pub mod queries;
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/pledgedata.ts"))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PledgeData {
|
||||
pub amount: Coin,
|
||||
pub block_time: u64,
|
||||
pub amount: Coin,
|
||||
pub block_time: u64,
|
||||
}
|
||||
|
||||
impl From<VestingPledgeData> for PledgeData {
|
||||
fn from(data: VestingPledgeData) -> Self {
|
||||
Self {
|
||||
amount: data.amount().into(),
|
||||
block_time: data.block_time().seconds(),
|
||||
fn from(data: VestingPledgeData) -> Self {
|
||||
Self {
|
||||
amount: data.amount().into(),
|
||||
block_time: data.block_time().seconds(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
fn and_then(data: VestingPledgeData) -> Option<Self> {
|
||||
Some(data.into())
|
||||
}
|
||||
fn and_then(data: VestingPledgeData) -> Option<Self> {
|
||||
Some(data.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/originalvestingresponse.ts")
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/originalvestingresponse.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: Coin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
amount: Coin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
}
|
||||
|
||||
impl From<VestingOriginalVestingResponse> for OriginalVestingResponse {
|
||||
fn from(data: VestingOriginalVestingResponse) -> Self {
|
||||
Self {
|
||||
amount: data.amount().into(),
|
||||
number_of_periods: data.number_of_periods(),
|
||||
period_duration: data.period_duration(),
|
||||
fn from(data: VestingOriginalVestingResponse) -> Self {
|
||||
Self {
|
||||
amount: data.amount().into(),
|
||||
number_of_periods: data.number_of_periods(),
|
||||
period_duration: data.period_duration(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/vestingaccountinfo.ts")
|
||||
test,
|
||||
ts(export, export_to = "../src/types/rust/vestingaccountinfo.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VestingAccountInfo {
|
||||
owner_address: String,
|
||||
staking_address: Option<String>,
|
||||
start_time: u64,
|
||||
periods: Vec<VestingPeriod>,
|
||||
coin: Coin,
|
||||
owner_address: String,
|
||||
staking_address: Option<String>,
|
||||
start_time: u64,
|
||||
periods: Vec<VestingPeriod>,
|
||||
coin: Coin,
|
||||
}
|
||||
|
||||
impl From<VestingAccount> for VestingAccountInfo {
|
||||
fn from(account: VestingAccount) -> Self {
|
||||
let mut periods = Vec::new();
|
||||
for period in account.periods() {
|
||||
periods.push(period.into());
|
||||
fn from(account: VestingAccount) -> Self {
|
||||
let mut periods = Vec::new();
|
||||
for period in account.periods() {
|
||||
periods.push(period.into());
|
||||
}
|
||||
Self {
|
||||
owner_address: account.owner_address().to_string(),
|
||||
staking_address: account.staking_address().map(|a| a.to_string()),
|
||||
start_time: account.start_time().seconds(),
|
||||
periods,
|
||||
coin: account.coin().into(),
|
||||
}
|
||||
}
|
||||
Self {
|
||||
owner_address: account.owner_address().to_string(),
|
||||
staking_address: account.staking_address().map(|a| a.to_string()),
|
||||
start_time: account.start_time().seconds(),
|
||||
periods,
|
||||
coin: account.coin().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/vestingperiod.ts"))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct VestingPeriod {
|
||||
start_time: u64,
|
||||
period_seconds: u64,
|
||||
start_time: u64,
|
||||
period_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<VestingVestingPeriod> for VestingPeriod {
|
||||
fn from(period: VestingVestingPeriod) -> Self {
|
||||
Self {
|
||||
start_time: period.start_time,
|
||||
period_seconds: period.period_seconds,
|
||||
fn from(period: VestingVestingPeriod) -> Self {
|
||||
Self {
|
||||
start_time: period.start_time,
|
||||
period_seconds: period.period_seconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,185 +13,161 @@ use super::{OriginalVestingResponse, PledgeData};
|
||||
|
||||
#[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<Coin, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.locked_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.locked_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn spendable_coins(
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.spendable_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.spendable_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vested_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.vested_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.vested_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.vesting_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.vesting_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_start_time(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<u64, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.vesting_start_time(vesting_account_address)
|
||||
.await?
|
||||
.seconds(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.vesting_start_time(vesting_account_address)
|
||||
.await?
|
||||
.seconds())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_end_time(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<u64, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.vesting_end_time(vesting_account_address)
|
||||
.await?
|
||||
.seconds(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.vesting_end_time(vesting_account_address)
|
||||
.await?
|
||||
.seconds())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn original_vesting(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<OriginalVestingResponse, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.original_vesting(vesting_account_address)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.original_vesting(vesting_account_address)
|
||||
.await?
|
||||
.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delegated_free(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Coin, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.delegated_free(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.delegated_free(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delegated_vesting(
|
||||
block_time: Option<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<Coin, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.delegated_vesting(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.delegated_vesting(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_get_mixnode_pledge(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<PledgeData>, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.get_mixnode_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.get_mixnode_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_get_gateway_pledge(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<PledgeData>, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.get_gateway_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then),
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.get_gateway_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_current_vesting_period(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Period, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.get_current_vesting_period(address)
|
||||
.await?,
|
||||
)
|
||||
Ok(nymd_client!(state)
|
||||
.get_current_vesting_period(address)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_account_info(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<VestingAccountInfo, BackendError> {
|
||||
Ok(nymd_client!(state).get_account(address).await?.into())
|
||||
Ok(nymd_client!(state).get_account(address).await?.into())
|
||||
}
|
||||
|
||||
+348
-358
@@ -17,450 +17,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(),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,126 +14,128 @@ use tokio::sync::RwLock;
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/appEnv.ts"))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct AppEnv {
|
||||
pub ADMIN_ADDRESS: Option<String>,
|
||||
pub SHOW_TERMINAL: Option<String>,
|
||||
pub ADMIN_ADDRESS: Option<String>,
|
||||
pub SHOW_TERMINAL: Option<String>,
|
||||
}
|
||||
|
||||
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 fn major_to_minor(amount: &str) -> Coin {
|
||||
let coin = Coin::new(amount, &Denom::Major);
|
||||
coin.to_minor()
|
||||
let coin = Coin::new(amount, &Denom::Major);
|
||||
coin.to_minor()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn minor_to_major(amount: &str) -> Coin {
|
||||
let coin = Coin::new(amount, &Denom::Minor);
|
||||
coin.to_major()
|
||||
let coin = Coin::new(amount, &Denom::Minor);
|
||||
coin.to_major()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn owns_mixnode(
|
||||
state: tauri::State<'_, Arc<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())
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/delegationresult.ts"))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DelegationResult {
|
||||
source_address: String,
|
||||
target_address: String,
|
||||
amount: Option<Coin>,
|
||||
source_address: String,
|
||||
target_address: String,
|
||||
amount: Option<Coin>,
|
||||
}
|
||||
|
||||
impl DelegationResult {
|
||||
pub fn new(source_address: &str, target_address: &str, amount: Option<Coin>) -> DelegationResult {
|
||||
DelegationResult {
|
||||
source_address: source_address.to_string(),
|
||||
target_address: target_address.to_string(),
|
||||
amount,
|
||||
pub fn new(
|
||||
source_address: &str,
|
||||
target_address: &str,
|
||||
amount: Option<Coin>,
|
||||
) -> DelegationResult {
|
||||
DelegationResult {
|
||||
source_address: source_address.to_string(),
|
||||
target_address: target_address.to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Delegation> for DelegationResult {
|
||||
fn from(delegation: Delegation) -> Self {
|
||||
DelegationResult {
|
||||
source_address: delegation.owner().to_string(),
|
||||
target_address: delegation.node_identity(),
|
||||
amount: Some(delegation.amount.into()),
|
||||
fn from(delegation: Delegation) -> Self {
|
||||
DelegationResult {
|
||||
source_address: delegation.owner().to_string(),
|
||||
target_address: delegation.node_identity(),
|
||||
amount: Some(delegation.amount.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/delegationevent.ts"))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum DelegationEvent {
|
||||
Delegate(DelegationResult),
|
||||
Undelegate(PendingUndelegate),
|
||||
Delegate(DelegationResult),
|
||||
Undelegate(PendingUndelegate),
|
||||
}
|
||||
|
||||
impl From<ContractDelegationEvent> for DelegationEvent {
|
||||
fn from(event: ContractDelegationEvent) -> Self {
|
||||
match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => DelegationEvent::Delegate(delegation.into()),
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => {
|
||||
DelegationEvent::Undelegate(pending_undelegate.into())
|
||||
}
|
||||
fn from(event: ContractDelegationEvent) -> Self {
|
||||
match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => {
|
||||
DelegationEvent::Delegate(delegation.into())
|
||||
}
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => {
|
||||
DelegationEvent::Undelegate(pending_undelegate.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/pendingundelegate.ts"))]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: String,
|
||||
delegate: String,
|
||||
proxy: Option<String>,
|
||||
block_height: u64,
|
||||
mix_identity: String,
|
||||
delegate: String,
|
||||
proxy: Option<String>,
|
||||
block_height: u64,
|
||||
}
|
||||
|
||||
impl From<ContractPendingUndelegate> for PendingUndelegate {
|
||||
fn from(pending_undelegate: ContractPendingUndelegate) -> Self {
|
||||
PendingUndelegate {
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
delegate: pending_undelegate.delegate().to_string(),
|
||||
proxy: pending_undelegate.proxy().map(|p| p.to_string()),
|
||||
block_height: pending_undelegate.block_height(),
|
||||
fn from(pending_undelegate: ContractPendingUndelegate) -> Self {
|
||||
PendingUndelegate {
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
delegate: pending_undelegate.delegate().to_string(),
|
||||
proxy: pending_undelegate.proxy().map(|p| p.to_string()),
|
||||
block_height: pending_undelegate.block_height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -58,6 +58,8 @@ pub struct RewardEstimationResponse {
|
||||
pub estimated_total_node_reward: u64,
|
||||
pub estimated_operator_reward: u64,
|
||||
pub estimated_delegators_reward: u64,
|
||||
pub estimated_node_profit: u64,
|
||||
pub estimated_operator_cost: u64,
|
||||
|
||||
pub reward_params: RewardParams,
|
||||
pub as_at: i64,
|
||||
|
||||
Reference in New Issue
Block a user