From b6ddaebe6700af802400e6ef9c0ab85831b60c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 6 Jun 2022 11:59:02 +0100 Subject: [PATCH] Attempt at dec-coinifying get_all_mix_delegations --- .../mixnet-contract/src/delegation.rs | 2 +- common/types/src/currency.rs | 7 + common/types/src/delegation.rs | 139 +++--- nym-wallet/nym-wallet-types/src/network.rs | 8 + .../src/operations/mixnet/delegate.rs | 395 +++++++++--------- 5 files changed, 293 insertions(+), 258 deletions(-) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index 645245d8e6..d8555def8f 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -33,7 +33,7 @@ pub struct Delegation { pub node_identity: IdentityKey, pub amount: Coin, pub block_height: u64, - pub proxy: Option, // proxy address used to delegate the funds on behalf of anouther address + pub proxy: Option, // proxy address used to delegate the funds on behalf of another address } impl Eq for Delegation {} diff --git a/common/types/src/currency.rs b/common/types/src/currency.rs index e10741a98f..610d0f5538 100644 --- a/common/types/src/currency.rs +++ b/common/types/src/currency.rs @@ -164,6 +164,13 @@ impl DecCoin { } } + pub fn zero>(denom: S) -> Self { + DecCoin { + denom: denom.into(), + amount: Decimal::zero(), + } + } + pub fn new_scaled_up>( base_amount: impl Into, denom: S, diff --git a/common/types/src/delegation.rs b/common/types/src/delegation.rs index 8b273529dd..bc660a855d 100644 --- a/common/types/src/delegation.rs +++ b/common/types/src/delegation.rs @@ -1,13 +1,11 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - +use crate::currency::DecCoin; +use crate::error::TypesError; use log::error; use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent; use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate; use mixnet_contract_common::Delegation as MixnetContractDelegation; - -use crate::currency::MajorCurrencyAmount; -use crate::error::TypesError; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( @@ -18,32 +16,48 @@ use crate::error::TypesError; pub struct Delegation { pub owner: String, pub node_identity: String, - pub amount: MajorCurrencyAmount, + pub amount: DecCoin, pub block_height: u64, - pub proxy: Option, // proxy address used to delegate the funds on behalf of anouther address + pub proxy: Option, // proxy address used to delegate the funds on behalf of another address +} + +impl Delegation { + pub fn from_mixnet_contract( + delegation: MixnetContractDelegation, + display_amount: DecCoin, + ) -> Self { + Delegation { + owner: delegation.owner.to_string(), + node_identity: delegation.node_identity, + amount: display_amount, + block_height: delegation.block_height, + proxy: delegation.proxy.map(|d| d.to_string()), + } + } } impl TryFrom for Delegation { type Error = TypesError; - fn try_from(value: MixnetContractDelegation) -> Result { - let MixnetContractDelegation { - owner, - node_identity, - amount, - block_height, - proxy, - } = value; - - let amount: MajorCurrencyAmount = amount.into(); - - Ok(Delegation { - owner: owner.into_string(), - node_identity, - amount, - block_height, - proxy: proxy.map(|p| p.into_string()), - }) + fn try_from(delegation: MixnetContractDelegation) -> Result { + todo!() + // let MixnetContractDelegation { + // owner, + // node_identity, + // amount, + // block_height, + // proxy, + // } = value; + // + // let amount: MajorCurrencyAmount = amount.into(); + // + // Ok(Delegation { + // owner: owner.into_string(), + // node_identity, + // amount, + // block_height, + // proxy: proxy.map(|p| p.into_string()), + // }) } } @@ -54,7 +68,7 @@ impl TryFrom for Delegation { )] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] pub struct DelegationRecord { - pub amount: MajorCurrencyAmount, + pub amount: DecCoin, pub block_height: u64, pub delegated_on_iso_datetime: String, } @@ -68,16 +82,16 @@ pub struct DelegationRecord { pub struct DelegationWithEverything { pub owner: String, pub node_identity: String, - pub amount: MajorCurrencyAmount, - pub total_delegation: Option, - pub pledge_amount: Option, + pub amount: DecCoin, + pub total_delegation: Option, + pub pledge_amount: Option, pub block_height: u64, pub delegated_on_iso_datetime: String, pub profit_margin_percent: Option, pub avg_uptime_percent: Option, pub stake_saturation: Option, pub proxy: Option, - pub accumulated_rewards: Option, + pub accumulated_rewards: Option, pub pending_events: Vec, pub history: Vec, } @@ -91,14 +105,14 @@ pub struct DelegationWithEverything { pub struct DelegationResult { source_address: String, target_address: String, - amount: Option, + amount: Option, } impl DelegationResult { pub fn new( source_address: &str, target_address: &str, - amount: Option, + amount: Option, ) -> DelegationResult { DelegationResult { source_address: source_address.to_string(), @@ -112,12 +126,13 @@ impl TryFrom for DelegationResult { type Error = TypesError; fn try_from(delegation: MixnetContractDelegation) -> Result { - let amount: MajorCurrencyAmount = delegation.amount.clone().into(); - Ok(DelegationResult { - source_address: delegation.owner().to_string(), - target_address: delegation.node_identity(), - amount: Some(amount), - }) + todo!() + // let amount: DecCoin = delegation.amount.clone().into(); + // Ok(DelegationResult { + // source_address: delegation.owner().to_string(), + // target_address: delegation.node_identity(), + // amount: Some(amount), + // }) } } @@ -142,7 +157,7 @@ pub struct DelegationEvent { pub kind: DelegationEventKind, pub node_identity: String, pub address: String, - pub amount: Option, + pub amount: Option, pub block_height: u64, } @@ -150,25 +165,27 @@ impl TryFrom for DelegationEvent { type Error = TypesError; fn try_from(event: ContractDelegationEvent) -> Result { - match event { - ContractDelegationEvent::Delegate(delegation) => { - let amount: MajorCurrencyAmount = delegation.amount.into(); - Ok(DelegationEvent { - kind: DelegationEventKind::Delegate, - block_height: delegation.block_height, - address: delegation.owner.into_string(), - node_identity: delegation.node_identity, - amount: Some(amount), - }) - } - ContractDelegationEvent::Undelegate(pending_undelegate) => Ok(DelegationEvent { - kind: DelegationEventKind::Undelegate, - block_height: pending_undelegate.block_height(), - address: pending_undelegate.delegate().into_string(), - node_identity: pending_undelegate.mix_identity(), - amount: None, - }), - } + todo!() + // + // match event { + // ContractDelegationEvent::Delegate(delegation) => { + // let amount: MajorCurrencyAmount = delegation.amount.into(); + // Ok(DelegationEvent { + // kind: DelegationEventKind::Delegate, + // block_height: delegation.block_height, + // address: delegation.owner.into_string(), + // node_identity: delegation.node_identity, + // amount: Some(amount), + // }) + // } + // ContractDelegationEvent::Undelegate(pending_undelegate) => Ok(DelegationEvent { + // kind: DelegationEventKind::Undelegate, + // block_height: pending_undelegate.block_height(), + // address: pending_undelegate.delegate().into_string(), + // node_identity: pending_undelegate.mix_identity(), + // amount: None, + // }), + // } } } @@ -228,6 +245,6 @@ pub fn from_contract_delegation_events( #[derive(Deserialize, Serialize)] pub struct DelegationsSummaryResponse { pub delegations: Vec, - pub total_delegations: MajorCurrencyAmount, - pub total_rewards: MajorCurrencyAmount, + pub total_delegations: DecCoin, + pub total_rewards: DecCoin, } diff --git a/nym-wallet/nym-wallet-types/src/network.rs b/nym-wallet/nym-wallet-types/src/network.rs index 556b3ca9ae..e5a10d0492 100644 --- a/nym-wallet/nym-wallet-types/src/network.rs +++ b/nym-wallet/nym-wallet-types/src/network.rs @@ -45,6 +45,14 @@ impl Network { Network::MAINNET => mainnet::MIX_DENOM.base, } } + + pub fn display_mix_denom(&self) -> &str { + match self { + Network::QA => qa::MIX_DENOM.display, + Network::SANDBOX => sandbox::MIX_DENOM.display, + Network::MAINNET => mainnet::MIX_DENOM.display, + } + } } impl Default for Network { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index d894c0b41f..be4ef4d41b 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -96,203 +96,206 @@ struct DelegationWithHistory { pub async fn get_all_mix_delegations( state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { - todo!("deal with this later :)"); - log::info!(">>> Get all mixnode delegations"); - // - // // TODO: add endpoint to validator API to get a single mix node bond - // let mixnodes = api_client!(state).get_mixnodes().await?; - // - // let address = nymd_client!(state).address().to_string(); - // - // let denom_minor = state.read().await.current_network().denom(); - // let denom: CurrencyDenom = denom_minor.clone().try_into()?; - // - // log::info!(" >>> Get delegations"); - // let delegations = nymd_client!(state) - // .get_delegator_delegations_paged(address.clone(), None, None) // get all delegations, ignoring paging - // .await? - // .delegations; - // log::info!(" <<< {} delegations", delegations.len()); - // - // // first get pending events from the mixnet contract (operations made with unlocked tokens) - // let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?; - // - // // then get pending events from the vesting contract (operations made with locked tokens) - // let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?; - // for event in pending_vesting_events { - // pending_events_for_account.push(event); - // } - // - // log::info!( - // " <<< {} pending delegation events for account", - // pending_events_for_account.len() - // ); - // - // let mut map: HashMap = HashMap::new(); - // - // for pending_event in pending_events_for_account.clone() { - // if delegations - // .iter() - // .any(|d| d.node_identity == pending_event.node_identity) - // { - // let amount = pending_event - // .amount - // .unwrap_or_else(|| DecCoin::zero(&denom)); - // let delegation = DelegationWithHistory { - // delegation: Delegation { - // amount: amount.clone(), - // node_identity: pending_event.node_identity, - // proxy: None, - // owner: pending_event.address, - // block_height: pending_event.block_height, - // }, - // amount_sum: amount, - // history: vec![], - // }; - // map.insert(delegation.delegation.node_identity.clone(), delegation); - // } - // } - // - // for d in delegations { - // // create record of delegation - // let delegated_on_iso_datetime = nymd_client!(state) - // .get_block_timestamp(Some(d.block_height as u32)) - // .await? - // .to_rfc3339(); - // let amount: DecCoin = d.amount.clone().into(); - // let record = DelegationRecord { - // amount: amount.clone(), - // block_height: d.block_height, - // delegated_on_iso_datetime, - // }; - // - // let entry = map - // .entry(d.node_identity.clone()) - // .or_insert(DelegationWithHistory { - // delegation: d.try_into()?, - // history: vec![], - // amount_sum: DecCoin::zero(&amount.denom), - // }); - // - // entry.history.push(record); - // entry.amount_sum = entry.amount_sum.clone() + amount; - // } - // - // let mut with_everything: Vec = vec![]; - // - // for item in map { - // let d = item.1.delegation; - // let history = item.1.history; - // let Delegation { - // owner, - // node_identity, - // amount, - // block_height, - // proxy, - // } = d; - // - // log::trace!( - // " --- Delegation: node_identity = {}, amount = {}", - // node_identity, - // amount - // ); - // - // let mixnode = mixnodes - // .iter() - // .find(|m| m.mix_node.identity_key == node_identity); - // - // let pledge_amount: Option = - // mixnode.and_then(|m| m.pledge_amount.clone().try_into().ok()); - // - // let total_delegation: Option = - // mixnode.and_then(|m| m.total_delegation.clone().try_into().ok()); - // - // let profit_margin_percent: Option = mixnode.map(|m| m.mix_node.profit_margin_percent); - // - // log::trace!(" >>> Get accumulated rewards: address = {}", address); - // let accumulated_rewards = match nymd_client!(state) - // .get_delegator_rewards(address.clone(), node_identity.clone(), proxy.clone()) - // .await - // { - // Ok(rewards) => { - // let reward = CosmWasmCoin { - // denom: denom_minor.to_string(), - // amount: rewards, - // }; - // let amount = DecCoin::from(reward); - // log::trace!(" <<< rewards = {}, amount = {}", rewards, amount); - // Some(amount) - // } - // Err(_) => { - // log::trace!(" <<< no rewards waiting"); - // None - // } - // }; - // - // let pending_events = - // filter_pending_events(&node_identity, pending_events_for_account.clone()); - // log::trace!( - // " --- pending events for mixnode = {}", - // pending_events.len() - // ); - // - // log::trace!( - // " >>> Get stake saturation: node_identity = {}", - // node_identity - // ); - // let stake_saturation = api_client!(state) - // .get_mixnode_stake_saturation(&node_identity) - // .await - // .ok() - // .map(|r| r.saturation); - // log::trace!(" <<< {:?}", stake_saturation); - // - // log::trace!( - // " >>> Get average uptime percentage: node_identity = {}", - // node_identity - // ); - // let avg_uptime_percent = api_client!(state) - // .get_mixnode_avg_uptime(&node_identity) - // .await - // .ok() - // .map(|r| r.avg_uptime); - // log::trace!(" <<< {:?}", avg_uptime_percent); - // - // log::trace!( - // " >>> Convert delegated on block height to timestamp: block_height = {}", - // d.block_height - // ); - // let timestamp = nymd_client!(state) - // .get_block_timestamp(Some(d.block_height as u32)) - // .await?; - // let delegated_on_iso_datetime = timestamp.to_rfc3339(); - // log::trace!( - // " <<< timestamp = {:?}, delegated_on_iso_datetime = {:?}", - // timestamp, - // delegated_on_iso_datetime - // ); - // - // with_everything.push(DelegationWithEverything { - // owner: owner.to_string(), - // node_identity: node_identity.to_string(), - // amount: item.1.amount_sum, - // block_height, - // proxy: proxy.clone(), - // delegated_on_iso_datetime, - // stake_saturation, - // accumulated_rewards, - // profit_margin_percent, - // pledge_amount, - // avg_uptime_percent, - // total_delegation, - // pending_events, - // history, - // }) - // } - // log::trace!("<<< {:?}", with_everything); - // - // Ok(with_everything) + + let guard = state.read().await; + let client = guard.current_client()?; + + // TODO: add endpoint to validator API to get a single mix node bond + let mixnodes = client.validator_api.get_mixnodes().await?; + + let address = client.nymd.address(); + let network = guard.current_network(); + let display_mix_denom = network.display_mix_denom(); + let base_mix_denom = network.base_mix_denom(); + + log::info!(" >>> Get delegations"); + let delegations = client.get_all_delegator_delegations(address).await?; + log::info!(" <<< {} delegations", delegations.len()); + + // first get pending events from the mixnet contract (operations made with unlocked tokens) + let mut pending_events_for_account = get_pending_delegation_events(state.clone()).await?; + + // then get pending events from the vesting contract (operations made with locked tokens) + let pending_vesting_events = get_pending_vesting_delegation_events(state.clone()).await?; + for event in pending_vesting_events { + pending_events_for_account.push(event); + } + + log::info!( + " <<< {} pending delegation events for account", + pending_events_for_account.len() + ); + + let mut map: HashMap = HashMap::new(); + + for pending_event in &pending_events_for_account { + if delegations + .iter() + .any(|d| d.node_identity == pending_event.node_identity) + { + let amount = pending_event + .amount + .clone() + .unwrap_or_else(|| DecCoin::zero(display_mix_denom)); + let delegation = DelegationWithHistory { + delegation: Delegation { + amount: amount.clone(), + node_identity: pending_event.node_identity.clone(), + proxy: None, // TODO: ask @MS about delegations via vesting contract => surely we'd have proxy there? + owner: pending_event.address.clone(), + block_height: pending_event.block_height, + }, + amount_sum: amount, + history: vec![], + }; + map.insert(delegation.delegation.node_identity.clone(), delegation); + } + } + + for d in delegations { + // create record of delegation + let delegated_on_iso_datetime = client + .nymd + .get_block_timestamp(Some(d.block_height as u32)) + .await? + .to_rfc3339(); + let amount = guard.attempt_convert_to_display_dec_coin(d.amount.clone().into())?; + + let record = DelegationRecord { + amount: amount.clone(), + block_height: d.block_height, + delegated_on_iso_datetime, + }; + + let entry = map + .entry(d.node_identity.clone()) + .or_insert(DelegationWithHistory { + delegation: Delegation::from_mixnet_contract(d, amount.clone()), + history: vec![], + amount_sum: DecCoin::zero(display_mix_denom), + }); + + debug_assert_eq!(entry.amount_sum.denom, amount.denom); + + entry.history.push(record); + entry.amount_sum.amount += amount.amount; + } + + let mut with_everything: Vec = vec![]; + + for item in map { + let d = item.1.delegation; + let history = item.1.history; + let Delegation { + owner, + node_identity, + amount, + block_height, + proxy, + } = d; + + log::trace!( + " --- Delegation: node_identity = {}, amount = {}", + node_identity, + amount + ); + + let mixnode = mixnodes + .iter() + .find(|m| m.mix_node.identity_key == node_identity); + + let pledge_amount = mixnode + .map(|m| guard.attempt_convert_to_display_dec_coin(m.pledge_amount.clone().into())) + .transpose()?; + + let total_delegation = mixnode + .map(|m| guard.attempt_convert_to_display_dec_coin(m.total_delegation.clone().into())) + .transpose()?; + + let profit_margin_percent: Option = mixnode.map(|m| m.mix_node.profit_margin_percent); + + log::trace!(" >>> Get accumulated rewards: address = {}", address); + let accumulated_rewards = match client + .nymd + .get_delegator_rewards(address.to_string(), node_identity.clone(), proxy.clone()) + .await + { + Ok(rewards) => { + let reward = Coin::new(rewards.u128(), base_mix_denom); + let amount = guard.attempt_convert_to_display_dec_coin(reward)?; + log::trace!(" <<< rewards = {}, amount = {}", rewards, amount); + Some(amount) + } + Err(_) => { + log::trace!(" <<< no rewards waiting"); + None + } + }; + + let pending_events = + filter_pending_events(&node_identity, pending_events_for_account.clone()); + log::trace!( + " --- pending events for mixnode = {}", + pending_events.len() + ); + + log::trace!( + " >>> Get stake saturation: node_identity = {}", + node_identity + ); + let stake_saturation = api_client!(state) + .get_mixnode_stake_saturation(&node_identity) + .await + .ok() + .map(|r| r.saturation); + log::trace!(" <<< {:?}", stake_saturation); + + log::trace!( + " >>> Get average uptime percentage: node_identity = {}", + node_identity + ); + let avg_uptime_percent = api_client!(state) + .get_mixnode_avg_uptime(&node_identity) + .await + .ok() + .map(|r| r.avg_uptime); + log::trace!(" <<< {:?}", avg_uptime_percent); + + log::trace!( + " >>> Convert delegated on block height to timestamp: block_height = {}", + d.block_height + ); + let timestamp = nymd_client!(state) + .get_block_timestamp(Some(d.block_height as u32)) + .await?; + let delegated_on_iso_datetime = timestamp.to_rfc3339(); + log::trace!( + " <<< timestamp = {:?}, delegated_on_iso_datetime = {:?}", + timestamp, + delegated_on_iso_datetime + ); + + with_everything.push(DelegationWithEverything { + owner: owner.to_string(), + node_identity: node_identity.to_string(), + amount: item.1.amount_sum, + block_height, + proxy: proxy.clone(), + delegated_on_iso_datetime, + stake_saturation, + accumulated_rewards, + profit_margin_percent, + pledge_amount, + avg_uptime_percent, + total_delegation, + pending_events, + history, + }) + } + log::trace!("<<< {:?}", with_everything); + + Ok(with_everything) } fn filter_pending_events(