Merge with develop

This commit is contained in:
Jędrzej Stuczyński
2022-05-31 10:50:02 +01:00
53 changed files with 5032 additions and 4504 deletions
+8
View File
@@ -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,
},
+26
View File
@@ -283,6 +283,32 @@ pub fn execute(
info,
)
}
ExecuteMsg::ClaimOperatorReward {} => {
crate::rewards::transactions::try_claim_operator_reward(deps, &env, &info)
}
ExecuteMsg::ClaimOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_claim_operator_reward_on_behalf(
deps, &env, &info, owner,
)
}
ExecuteMsg::ClaimDelegatorReward { mix_identity } => {
crate::rewards::transactions::try_claim_delegator_reward(
deps,
&env,
&info,
&mix_identity,
)
}
ExecuteMsg::ClaimDelegatorRewardOnBehalf {
mix_identity,
owner,
} => crate::rewards::transactions::try_claim_delegator_reward_on_behalf(
deps,
&env,
&info,
owner,
&mix_identity,
),
}
}
+2 -2
View File
@@ -153,8 +153,8 @@ pub enum ContractError {
#[from]
source: MixnetContractError,
},
#[error("No rewards to claim for mixnode {identity} for delegate {delegate}")]
NoRewardsToClaim { identity: String, delegate: String },
#[error("No rewards to claim for mixnode {identity} for {address}")]
NoRewardsToClaim { identity: String, address: String },
#[error("Epoch not initialized yet!")]
EpochNotInitialized,
+198 -9
View File
@@ -15,9 +15,13 @@ use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond};
use crate::rewards::helpers;
use crate::support::helpers::is_authorized;
use config::defaults::DENOM;
use cosmwasm_std::{Addr, Api, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128};
use cosmwasm_std::{
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Order, Response,
Storage, Uint128,
};
use cw_storage_plus::Bound;
use mixnet_contract_common::events::{
new_claim_delegator_reward_event, new_claim_operator_reward_event,
new_compound_delegator_reward_event, new_compound_operator_reward_event,
new_mix_operator_rewarding_event, new_not_found_mix_operator_rewarding_event,
new_too_fresh_bond_mix_operator_rewarding_event, new_zero_uptime_mix_operator_rewarding_event,
@@ -27,6 +31,186 @@ use mixnet_contract_common::reward_params::{NodeEpochRewards, NodeRewardParams,
use mixnet_contract_common::{Delegation, IdentityKey, RewardingStatus};
use mixnet_contract_common::RewardingResult;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use vesting_contract_common::one_ucoin;
// All four of the below methods need to do the following things:
// 1. Calculate currently available rewards
// 2. Send the rewards back to whoever claimed them
// 3. Set the LAST_CLAIMED_HEIGHT to the current height
pub fn try_claim_operator_reward(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
) -> Result<Response, ContractError> {
_try_claim_operator_reward(deps.storage, deps.api, env, &info.sender.to_string(), None)
}
pub fn try_claim_operator_reward_on_behalf(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
owner: String,
) -> Result<Response, ContractError> {
_try_claim_operator_reward(
deps.storage,
deps.api,
env,
&owner,
Some(info.sender.clone()),
)
}
fn _try_claim_operator_reward(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
owner: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = api.addr_validate(owner)?;
let bond = match crate::mixnodes::storage::mixnodes()
.idx
.owner
.item(storage, owner.clone())?
{
Some(record) => record.1,
None => return Err(ContractError::NoAssociatedMixNodeBond { owner }),
};
if proxy != bond.proxy {
return Err(ContractError::ProxyMismatch {
existing: bond
.proxy
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
let reward = calculate_operator_reward(storage, api, &owner, &bond)?;
OPERATOR_REWARD_CLAIMED_HEIGHT.save(
storage,
(owner.to_string(), bond.identity().to_string()),
&env.block.height,
)?;
if reward.is_zero() {
return Err(ContractError::NoRewardsToClaim {
identity: bond.identity().to_string(),
address: owner.to_string(),
});
}
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
amount: coins(reward.u128(), DENOM),
};
let mut response = Response::default()
.add_message(return_tokens)
.add_event(new_claim_operator_reward_event(&owner, reward));
if let Some(proxy) = proxy {
let msg = Some(VestingContractExecuteMsg::TrackReward {
address: owner.to_string(),
amount: Coin::new(reward.u128(), DENOM),
});
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
response = response.add_message(wasm_msg);
}
Ok(response)
}
pub fn _try_claim_delegator_reward(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
owner: &str,
mix_identity: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = api.addr_validate(owner)?;
let key = mixnet_contract_common::delegation::generate_storage_key(&owner, proxy.as_ref());
let reward = calculate_delegator_reward(storage, api, key.clone(), mix_identity)?;
DELEGATOR_REWARD_CLAIMED_HEIGHT.save(
storage,
(key, mix_identity.to_string()),
&env.block.height,
)?;
if reward.is_zero() {
return Err(ContractError::NoRewardsToClaim {
identity: mix_identity.to_string(),
address: owner.to_string(),
});
}
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
amount: coins(reward.u128(), DENOM),
};
let mut response =
Response::default()
.add_message(return_tokens)
.add_event(new_claim_delegator_reward_event(
&owner,
&proxy,
reward,
mix_identity,
));
if let Some(proxy) = proxy {
let msg = Some(VestingContractExecuteMsg::TrackReward {
address: owner.to_string(),
amount: Coin::new(reward.u128(), DENOM),
});
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
response = response.add_message(wasm_msg);
}
Ok(response)
}
pub fn try_claim_delegator_reward_on_behalf(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
owner: String,
mix_identity: &str,
) -> Result<Response, ContractError> {
_try_claim_delegator_reward(
deps.storage,
deps.api,
env,
&owner,
mix_identity,
Some(info.sender.clone()),
)
}
pub fn try_claim_delegator_reward(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
mix_identity: &str,
) -> Result<Response, ContractError> {
_try_claim_delegator_reward(
deps.storage,
deps.api,
env,
&info.sender.to_string(),
mix_identity,
None,
)
}
pub fn try_compound_operator_reward_on_behalf(
deps: DepsMut,
@@ -449,7 +633,7 @@ pub(crate) fn try_reward_mixnode(
let node_delegation = current_bond.total_delegation.amount;
// check if it has non-zero uptime
if params.uptime() == 0 {
if params.uptime() == Uint128::zero() {
storage::REWARDING_STATUS.save(
deps.storage,
(epoch.id(), mix_identity.clone()),
@@ -1381,7 +1565,7 @@ pub mod tests {
let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity)
.unwrap()
.unwrap();
let mix_1_uptime = 100;
let mix_1_uptime = 90;
let epoch = Interval::init_epoch(env.clone());
save_epoch(&mut deps.storage, &epoch).unwrap();
@@ -1395,7 +1579,7 @@ pub mod tests {
params.set_reward_blockstamp(env.block.height);
assert_eq!(params.performance(), U128::from_num(1u32));
assert_eq!(params.performance(), U128::from_num(0.8999999999999999));
let mix_1_reward_result = mix_1.reward(&params);
@@ -1407,9 +1591,14 @@ pub mod tests {
mix_1_reward_result.lambda(),
U128::from_num(0.0000133333333333f64)
);
assert_eq!(mix_1_reward_result.reward().int(), 259114u128);
assert_eq!(mix_1_reward_result.reward().int(), 233202u128);
assert_eq!(mix_1.node_profit(&params).int(), 203558u128);
assert_eq!(mix_1.node_profit(&params).int(), 183202u128);
assert_ne!(
mix_1_reward_result.reward(),
mix_1.node_profit(&params).int()
);
let mix1_operator_reward = mix_1.operator_reward(&params);
@@ -1417,9 +1606,9 @@ pub mod tests {
let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), &params);
assert_eq!(mix1_operator_reward, 167513);
assert_eq!(mix1_delegator1_reward, 73280);
assert_eq!(mix1_delegator2_reward, 18320);
assert_eq!(mix1_operator_reward, 150761);
assert_eq!(mix1_delegator1_reward, 65952);
assert_eq!(mix1_delegator2_reward, 16488);
assert_eq!(
mix_1_reward_result.reward().int(),
+40 -1
View File
@@ -13,7 +13,8 @@ use mixnet_contract_common::{Gateway, IdentityKey, MixNode};
use vesting_contract_common::events::{
new_ownership_transfer_event, new_periodic_vesting_account_event,
new_staking_address_update_event, new_track_gateway_unbond_event,
new_track_mixnode_unbond_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
new_vested_coins_withdraw_event,
};
use vesting_contract_common::messages::{
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
@@ -46,6 +47,13 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::TrackReward { amount, address } => {
try_track_reward(deps, info, amount, &address)
}
ExecuteMsg::ClaimOperatorReward {} => try_claim_operator_reward(deps, info),
ExecuteMsg::ClaimDelegatorReward { mix_identity } => {
try_claim_delegator_reward(deps, info, mix_identity)
}
ExecuteMsg::CompoundDelegatorReward { mix_identity } => {
try_compound_delegator_reward(mix_identity, info, deps)
}
@@ -278,6 +286,20 @@ pub fn try_track_unbond_mixnode(
Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
}
fn try_track_reward(
deps: DepsMut<'_>,
info: MessageInfo,
amount: Coin,
address: &str,
) -> Result<Response, ContractError> {
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
return Err(ContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(address, deps.storage, deps.api)?;
account.track_reward(amount, deps.storage)?;
Ok(Response::new().add_event(new_track_reward_event()))
}
fn try_track_undelegation(
address: &str,
mix_identity: IdentityKey,
@@ -314,6 +336,23 @@ fn try_compound_delegator_reward(
account.try_compound_delegator_reward(mix_identity, deps.storage)
}
fn try_claim_operator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_claim_operator_reward(deps.storage)
}
fn try_claim_delegator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
mix_identity: String,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_claim_delegator_reward(mix_identity, deps.storage)
}
fn try_undelegate_from_mixnode(
mix_identity: IdentityKey,
info: MessageInfo,
@@ -8,6 +8,8 @@ pub trait MixnodeBondingAccount {
storage: &dyn Storage,
) -> Result<Response, ContractError>;
fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result<Response, ContractError>;
fn try_bond_mixnode(
&self,
mix_node: MixNode,
@@ -3,6 +3,12 @@ use cosmwasm_std::{Coin, Env, Response, Storage, Uint128};
use mixnet_contract_common::IdentityKey;
pub trait DelegatingAccount {
fn try_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
storage: &dyn Storage,
) -> Result<Response, ContractError>;
fn try_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
@@ -73,4 +73,5 @@ pub trait VestingAccount {
to_address: Option<Addr>,
storage: &mut dyn Storage,
) -> Result<(), ContractError>;
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>;
}
@@ -13,6 +13,25 @@ use vesting_contract_common::one_ucoin;
use super::Account;
impl DelegatingAccount for Account {
fn try_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
storage: &dyn Storage,
) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::ClaimDelegatorRewardOnBehalf {
owner: self.owner_address().to_string(),
mix_identity,
};
let compound_delegator_reward_msg = wasm_execute(
MIXNET_CONTRACT_ADDRESS.load(storage)?,
&msg,
vec![one_ucoin()],
)?;
Ok(Response::new().add_message(compound_delegator_reward_msg))
}
fn try_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
@@ -14,6 +14,20 @@ use vesting_contract_common::PledgeData;
use super::Account;
impl MixnodeBondingAccount for Account {
fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::ClaimOperatorRewardOnBehalf {
owner: self.owner_address().into_string(),
};
let compound_operator_reward_msg = wasm_execute(
MIXNET_CONTRACT_ADDRESS.load(storage)?,
&msg,
vec![one_ucoin()],
)?;
Ok(Response::new().add_message(compound_operator_reward_msg))
}
fn try_compound_operator_reward(
&self,
storage: &dyn Storage,
@@ -8,6 +8,13 @@ use vesting_contract_common::{OriginalVestingResponse, Period};
use super::Account;
impl VestingAccount for Account {
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
let current_balance = self.load_balance(storage)?;
let new_balance = current_balance + amount.amount;
self.save_balance(new_balance, storage)?;
Ok(())
}
fn locked_coins(
&self,
block_time: Option<Timestamp>,
+1 -1
View File
@@ -36,6 +36,6 @@ pub(crate) async fn retrieve_mixnode_econ_stats(
estimated_total_node_reward: reward_estimation.estimated_total_node_reward,
estimated_operator_reward: reward_estimation.estimated_operator_reward,
estimated_delegators_reward: reward_estimation.estimated_delegators_reward,
current_interval_uptime: reward_estimation.reward_params.node.uptime() as u8,
current_interval_uptime: reward_estimation.reward_params.node.uptime().u128() as u8,
})
}
-13
View File
@@ -1,13 +0,0 @@
max_width = 100
hard_tabs = false
tab_spaces = 2
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2018"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
+1 -1
View File
@@ -1,3 +1,3 @@
fn main() {
tauri_build::build()
tauri_build::build()
}
+269 -269
View File
@@ -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));
}
}
}
}
+397 -400
View File
@@ -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
View File
@@ -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
View File
@@ -1,6 +1,6 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use 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();
}
+18 -18
View File
@@ -3,25 +3,25 @@ use tauri::Menu;
use tauri::{MenuItem, Submenu};
pub trait AddDefaultSubmenus {
fn add_default_app_submenu_if_macos(self) -> Self;
fn add_default_app_submenu_if_macos(self) -> Self;
}
impl AddDefaultSubmenus for Menu {
fn add_default_app_submenu_if_macos(self) -> Menu {
#[cfg(target_os = "macos")]
return self.add_submenu(Submenu::new(
"Menu",
Menu::new()
.add_native_item(MenuItem::Copy)
.add_native_item(MenuItem::Cut)
.add_native_item(MenuItem::Paste)
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::HideOthers)
.add_native_item(MenuItem::SelectAll)
.add_native_item(MenuItem::ShowAll)
.add_native_item(MenuItem::Quit),
));
#[cfg(not(target_os = "macos"))]
return self;
}
fn add_default_app_submenu_if_macos(self) -> Menu {
#[cfg(target_os = "macos")]
return self.add_submenu(Submenu::new(
"Menu",
Menu::new()
.add_native_item(MenuItem::Copy)
.add_native_item(MenuItem::Cut)
.add_native_item(MenuItem::Paste)
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::HideOthers)
.add_native_item(MenuItem::SelectAll)
.add_native_item(MenuItem::ShowAll)
.add_native_item(MenuItem::Quit),
));
#[cfg(not(target_os = "macos"))]
return self;
}
}
+32 -32
View File
@@ -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,
}
}
}
}
+58 -58
View File
@@ -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
View File
@@ -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(),
],
)
}
}
+65 -63
View File
@@ -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()
}
}
+6 -8
View File
@@ -148,15 +148,13 @@ pub(crate) async fn get_mixnode_reward_estimation(
let reward_params = RewardParams::new(reward_params, node_reward_params);
match bond.estimate_reward(&reward_params) {
Ok((
estimated_total_node_reward,
estimated_operator_reward,
estimated_delegators_reward,
)) => {
Ok(reward_estimate) => {
let reponse = RewardEstimationResponse {
estimated_total_node_reward,
estimated_operator_reward,
estimated_delegators_reward,
estimated_total_node_reward: reward_estimate.total_node_reward,
estimated_operator_reward: reward_estimate.operator_reward,
estimated_delegators_reward: reward_estimate.delegators_reward,
estimated_node_profit: reward_estimate.node_profit,
estimated_operator_cost: reward_estimate.operator_cost,
reward_params,
as_at,
};
@@ -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,