From 8318002b0afc604333ec51169337515e5ff05686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 25 Apr 2022 13:01:45 +0200 Subject: [PATCH] Bugfix/delegation reconcile (#1219) * Passing proxy value when attempting to compound delegator reward * Do not attempt to delegate reward to mixnode if its zero * Additional guards against sending 0 tokens * Removed sign of sloppiness * Fixes to rewards and delegation events storage * Remove block count check, epoch cannot be advanced while in progress * Add reward compound ops to vesting contract * Migration to remove 0 value delegationns Co-authored-by: durch --- .../validator-client/src/client.rs | 3 +- .../validator-client/src/nymd/mod.rs | 2 + .../mixnet-contract/src/msg.rs | 1 + .../vesting-contract/src/messages.rs | 4 ++ contracts/mixnet/src/contract.rs | 34 ++++++++- .../mixnet/src/delegations/transactions.rs | 71 ++++++++++--------- contracts/mixnet/src/error.rs | 3 + contracts/mixnet/src/interval/transactions.rs | 25 ------- contracts/mixnet/src/rewards/queries.rs | 18 +++-- contracts/mixnet/src/rewards/storage.rs | 3 +- contracts/mixnet/src/rewards/transactions.rs | 69 +++++++++--------- contracts/vesting/src/contract.rs | 21 ++++++ .../vesting/src/traits/bonding_account.rs | 5 ++ .../vesting/src/traits/delegating_account.rs | 6 ++ .../src/vesting/account/delegating_account.rs | 18 +++++ .../account/mixnode_bonding_account.rs | 17 +++++ .../src/operations/mixnet/delegate.rs | 3 +- validator-api/src/nymd_client.rs | 3 +- 18 files changed, 201 insertions(+), 105 deletions(-) diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 71314609ab..c784dd709f 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -264,13 +264,14 @@ impl Client { &self, address: String, mix_identity: IdentityKey, + proxy: Option, ) -> Result where C: CosmWasmClient + Sync, { Ok(self .nymd - .get_delegator_rewards(address, mix_identity) + .get_delegator_rewards(address, mix_identity, proxy) .await? .u128()) } diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 4a6cc06efb..530f202079 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -329,6 +329,7 @@ impl NymdClient { &self, address: String, mix_identity: IdentityKey, + proxy: Option, ) -> Result where C: CosmWasmClient + Sync, @@ -336,6 +337,7 @@ impl NymdClient { let request = QueryMsg::QueryDelegatorReward { address, mix_identity, + proxy, }; self.client .query_contract_smart(self.mixnet_contract_address()?, &request) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 327eb09798..9cc651030c 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -171,6 +171,7 @@ pub enum QueryMsg { QueryDelegatorReward { address: String, mix_identity: IdentityKey, + proxy: Option, }, GetPendingDelegationEvents { owner_address: String, diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index dc478ed751..4ed3114b4d 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -49,6 +49,10 @@ impl VestingSpecification { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { + CompoundDelegatorReward { + mix_identity: String, + }, + CompoundOperatorReward {}, UpdateMixnodeConfig { profit_margin_percent: u8, }, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 0381a67362..adc1a19357 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -7,6 +7,7 @@ use crate::delegations::queries::query_mixnode_delegation; use crate::delegations::queries::{ query_mixnode_delegations_paged, query_pending_delegation_events, }; +use crate::delegations::storage::delegations; use crate::error::ContractError; use crate::gateways::queries::query_gateways_paged; use crate::gateways::queries::query_owns_gateway; @@ -32,6 +33,7 @@ use crate::rewards::storage as rewards_storage; use cosmwasm_std::{ entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, }; +use mixnet_contract_common::mixnode::DelegationEvent; use mixnet_contract_common::{ ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, }; @@ -364,10 +366,12 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result to_binary(&crate::rewards::queries::query_delegator_reward( deps, address, mix_identity, + proxy, )?), QueryMsg::GetPendingDelegationEvents { owner_address, @@ -389,7 +393,35 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result, _env: Env, _msg: MigrateMsg) -> Result { +pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + // if there exists any delegation of 0 value, remove it + let zero_delegations = delegations() + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .filter_map(|r| r.ok()) + .filter(|(_k, v)| v.amount.amount == Uint128::zero()) + .map(|(k, _v)| k) + .collect::>(); + + for delegation in &zero_delegations { + delegations().remove(deps.storage, delegation.clone())?; + } + + // similarly, if there exists any event that is scheduled to insert/remove delegation with 0 value, purge it + let delegate_events_to_purge = crate::delegations::storage::PENDING_DELEGATION_EVENTS + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .filter_map(|r| r.ok()) + .filter(|(_k, v)| match v { + DelegationEvent::Delegate(d) => d.amount.amount == Uint128::zero(), + _ => false, + }) + .map(|(k, _v)| k) + .collect::>(); + + for delegation_event in delegate_events_to_purge { + crate::delegations::storage::PENDING_DELEGATION_EVENTS + .remove(deps.storage, delegation_event); + } + Ok(Default::default()) } diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index c43411eb80..c5083d7cc3 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -28,13 +28,12 @@ pub fn try_reconcile_all_delegation_events( return Err(ContractError::Unauthorized); } - _try_reconcile_all_delegation_events(deps.storage, deps.api) + _try_reconcile_all_delegation_events(deps.storage) } // TODO: Error handling? pub(crate) fn _try_reconcile_all_delegation_events( storage: &mut dyn Storage, - api: &dyn Api, ) -> Result { let pending_delegation_events = PENDING_DELEGATION_EVENTS .range(storage, None, None, Order::Ascending) @@ -46,12 +45,15 @@ pub(crate) fn _try_reconcile_all_delegation_events( for (key, delegation_event) in pending_delegation_events { match delegation_event { DelegationEvent::Delegate(delegation) => { + // if for some reason the delegation is zero, don't do anything since it should be a no-op anyway + if delegation.amount.amount == Uint128::zero() { + continue; + } let event = try_reconcile_delegation(storage, delegation)?; response = response.add_event(event); } DelegationEvent::Undelegate(pending_undelegate) => { - let undelegate_response = - try_reconcile_undelegation(storage, api, &pending_undelegate)?; + let undelegate_response = try_reconcile_undelegation(storage, &pending_undelegate)?; response = response.add_event(undelegate_response.event); if let Some(msg) = undelegate_response.bank_msg { response = response.add_message(msg); @@ -240,7 +242,6 @@ pub struct ReconcileUndelegateResponse { pub(crate) fn try_reconcile_undelegation( storage: &mut dyn Storage, - api: &dyn Api, pending_undelegate: &PendingUndelegate, ) -> Result { let delegation_map = storage::delegations(); @@ -266,13 +267,10 @@ pub(crate) fn try_reconcile_undelegation( }); } - let reward = crate::rewards::transactions::_try_compound_delegator_reward( - pending_undelegate.block_height(), - api, + let reward = crate::rewards::transactions::calculate_delegator_reward( storage, - pending_undelegate.delegate().as_str(), + pending_undelegate.proxy_storage_key(), &pending_undelegate.mix_identity(), - None, )?; // Might want to introduce paging here @@ -318,13 +316,18 @@ pub(crate) fn try_reconcile_undelegation( )?; } - let bank_msg = BankMsg::Send { - to_address: pending_undelegate - .proxy() - .as_ref() - .unwrap_or(&pending_undelegate.delegate()) - .to_string(), - amount: coins(total_delegation.u128(), DENOM), + // don't add a bank message if it would have resulted in attempting to send 0 tokens + let bank_msg = if total_delegation != Uint128::zero() { + Some(BankMsg::Send { + to_address: pending_undelegate + .proxy() + .as_ref() + .unwrap_or(&pending_undelegate.delegate()) + .to_string(), + amount: coins(total_delegation.u128(), DENOM), + }) + } else { + None }; mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>( @@ -362,7 +365,7 @@ pub(crate) fn try_reconcile_undelegation( ); Ok(ReconcileUndelegateResponse { - bank_msg: Some(bank_msg), + bank_msg, wasm_msg, event, }) @@ -502,7 +505,7 @@ mod tests { ) .is_ok()); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); let expected = Delegation::new( delegation_owner.clone(), @@ -581,7 +584,7 @@ mod tests { ) .is_ok()); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); let expected = Delegation::new( delegation_owner.clone(), @@ -644,7 +647,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); // let expected = Delegation::new( // delegation_owner.clone(), @@ -699,7 +702,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); assert_eq!( initial_height, @@ -720,7 +723,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); let delegations = crate::delegations::queries::query_mixnode_delegation( &deps.storage, @@ -765,7 +768,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); assert_eq!( initial_height, @@ -786,7 +789,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); assert_eq!( initial_height, @@ -874,7 +877,7 @@ mod tests { ) .is_ok()); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); let expected1 = Delegation::new( delegation_owner.clone(), @@ -939,7 +942,7 @@ mod tests { identity.clone(), ) .is_ok()); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); // node's "total_delegation" is sum of both assert_eq!( @@ -969,7 +972,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); @@ -1057,7 +1060,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); let _delegation = query_mixnode_delegation( &deps.storage, @@ -1127,7 +1130,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); let delegation = query_mixnode_delegation( &deps.storage, @@ -1162,7 +1165,7 @@ mod tests { ) ); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); assert!(test_helpers::read_delegation( &deps.storage, @@ -1195,7 +1198,7 @@ mod tests { ) .is_ok()); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); assert!(try_delegate_to_mixnode( deps.as_mut(), @@ -1205,7 +1208,7 @@ mod tests { ) .is_ok()); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); // sender1 undelegates try_remove_delegation_from_mixnode( @@ -1216,7 +1219,7 @@ mod tests { ) .unwrap(); - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); + _try_reconcile_all_delegation_events(&mut deps.storage).unwrap(); // but total delegation should still equal to what sender2 sent // node's "total_delegation" is sum of both assert_eq!( diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index 3fcbb8df10..102be015e0 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -158,4 +158,7 @@ pub enum ContractError { #[error("Epoch not initialized yet!")] EpochNotInitialized, + + #[error("Invalid address: {0}")] + InvalidAddress(String), } diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index ba2e63daeb..c9340846b7 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -43,16 +43,6 @@ pub fn try_write_rewarded_set( } let block_height = env.block.height; - - if let Some(last_update) = storage::CURRENT_REWARDED_SET_HEIGHT.may_load(deps.storage)? { - if last_update + crate::constants::REWARDED_SET_REFRESH_BLOCKS > block_height { - return Err(ContractError::TooFrequentRewardedSetUpdate { - last_update, - minimum_delay: crate::constants::REWARDED_SET_REFRESH_BLOCKS, - current_height: block_height, - }); - } - } let num_nodes = rewarded_set.len(); storage::save_rewarded_set(deps.storage, block_height, active_set_size, rewarded_set)?; @@ -184,21 +174,6 @@ mod tests { // cannot be performed too soon after a previous update env.block.height = last_update + 1; - assert_eq!( - Err(ContractError::TooFrequentRewardedSetUpdate { - last_update, - minimum_delay: crate::constants::REWARDED_SET_REFRESH_BLOCKS, - current_height: last_update + 1, - }), - try_write_rewarded_set( - deps.as_mut(), - env.clone(), - authorised_sender.clone(), - full_rewarded_set.clone(), - current_state.params.mixnode_active_set_size - ) - ); - // after successful rewarded set write, all internal storage structures are updated appropriately env.block.height = last_update + crate::constants::REWARDED_SET_REFRESH_BLOCKS; let expected_response = Response::new().add_event(new_change_rewarded_set_event( diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index 1c956d8b8f..4c087a7751 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -46,13 +46,19 @@ pub fn query_delegator_reward( deps: Deps, owner: String, mix_identity: IdentityKey, + proxy: Option, ) -> Result { - let owner_address = deps.api.addr_validate(&owner)?; - super::transactions::calculate_delegator_reward( - deps.storage, - owner_address.as_str(), - &mix_identity, - ) + let proxy = proxy.map(|p| { + deps.api + .addr_validate(&p) + .map_err(|_| ContractError::InvalidAddress(p)) + .expect("proxy address is invalid") + }); + let key = mixnet_contract_common::delegation::generate_storage_key( + &deps.api.addr_validate(&owner)?, + proxy.as_ref(), + ); + super::transactions::calculate_delegator_reward(deps.storage, key, &mix_identity) } #[cfg(test)] diff --git a/contracts/mixnet/src/rewards/storage.rs b/contracts/mixnet/src/rewards/storage.rs index b9055db900..f38e0cebf8 100644 --- a/contracts/mixnet/src/rewards/storage.rs +++ b/contracts/mixnet/src/rewards/storage.rs @@ -14,7 +14,8 @@ pub(crate) const REWARD_POOL: Item<'_, Uint128> = Item::new("pool"); // TODO: Do we need a migration for this? pub(crate) const REWARDING_STATUS: Map<'_, (u32, IdentityKey), RewardingStatus> = Map::new("rm"); -pub(crate) const DELEGATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> = +// This has to be a byte vector due to proxy delegastions and rewarding +pub(crate) const DELEGATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Vec, IdentityKey), BlockHeight> = Map::new("drc"); pub(crate) const OPERATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> = Map::new("orc"); diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index b3937d7bd7..8fb2afd005 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -1,7 +1,10 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use super::storage::{self, epoch_reward_params_for_id, DELEGATOR_REWARD_CLAIMED_HEIGHT}; +use super::storage::{ + self, epoch_reward_params_for_id, DELEGATOR_REWARD_CLAIMED_HEIGHT, + OPERATOR_REWARD_CLAIMED_HEIGHT, +}; use crate::constants; use crate::delegations::storage as delegations_storage; use crate::delegations::transactions::_try_delegate_to_mixnode; @@ -80,9 +83,9 @@ pub fn _try_compound_operator_reward( block_height, )?; - DELEGATOR_REWARD_CLAIMED_HEIGHT.save( + OPERATOR_REWARD_CLAIMED_HEIGHT.save( storage, - (bond.identity().to_string(), owner.to_string()), + (owner.to_string(), bond.identity().to_string()), &block_height, )?; @@ -198,31 +201,36 @@ pub fn _try_compound_delegator_reward( mix_identity: &str, proxy: Option, ) -> Result { - let reward = calculate_delegator_reward(storage, owner_address, mix_identity)?; - if _try_delegate_to_mixnode( - storage, - api, - block_height, - mix_identity, - owner_address, - Coin { - amount: reward, - denom: DENOM.to_string(), - }, - proxy, - ) - .is_ok() + let key = mixnet_contract_common::delegation::generate_storage_key( + &api.addr_validate(owner_address)?, + proxy.as_ref(), + ); + let reward = calculate_delegator_reward(storage, key.clone(), mix_identity)?; + if reward != Uint128::zero() + && _try_delegate_to_mixnode( + storage, + api, + block_height, + mix_identity, + owner_address, + Coin { + amount: reward, + denom: DENOM.to_string(), + }, + proxy, + ) + .is_ok() { // Node exists all is well, life goes on, if it does not exist we'll just return the reward to the caller as there is nothing to do on the bond if let Some(mut bond) = mixnodes().may_load(storage, mix_identity)? { bond.accumulated_rewards = Some(bond.accumulated_rewards() - reward); mixnodes().save(storage, mix_identity, &bond, block_height)?; } - }; + } DELEGATOR_REWARD_CLAIMED_HEIGHT.save( storage, - (mix_identity.to_string(), owner_address.to_string()), + (key, mix_identity.to_string()), &block_height, )?; @@ -234,20 +242,17 @@ pub fn _try_compound_delegator_reward( // + last_reward_claimed height is correctly used pub fn calculate_delegator_reward( storage: &dyn Storage, - owner_address: &str, + key: Vec, mix_identity: &str, ) -> Result { let last_claimed_height = storage::DELEGATOR_REWARD_CLAIMED_HEIGHT - .load( - storage, - (owner_address.to_string(), mix_identity.to_string()), - ) + .load(storage, (key.clone(), mix_identity.to_string())) .unwrap_or(0); // Get delegations newer then last_claimed_height, it would be nice to also fold this into the iteration bellow but it should be ok for now, as // I doubt folks refresh their delegations often let delegations = delegations_storage::delegations() - .prefix((mix_identity.to_string(), owner_address.as_bytes().to_vec())) + .prefix((mix_identity.to_string(), key)) .range(storage, None, None, Order::Descending) .filter_map(|record| record.ok()) .filter(|(height, _)| last_claimed_height <= *height) @@ -866,11 +871,8 @@ pub mod tests { ) .unwrap(); - crate::delegations::transactions::_try_reconcile_all_delegation_events( - &mut deps.storage, - &deps.api, - ) - .unwrap(); + crate::delegations::transactions::_try_reconcile_all_delegation_events(&mut deps.storage) + .unwrap(); let info = mock_info(rewarding_validator_address.as_ref(), &[]); env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING; @@ -985,11 +987,8 @@ pub mod tests { ) .unwrap(); - crate::delegations::transactions::_try_reconcile_all_delegation_events( - &mut deps.storage, - &deps.api, - ) - .unwrap(); + crate::delegations::transactions::_try_reconcile_all_delegation_events(&mut deps.storage) + .unwrap(); let info = mock_info(rewarding_validator_address.as_ref(), &[]); env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING; diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 693979b433..db9e73669a 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -46,6 +46,10 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::CompoundDelegatorReward { mix_identity } => { + try_compound_delegator_reward(mix_identity, info, deps) + } + ExecuteMsg::CompoundOperatorReward {} => try_compound_operator_reward(info, deps), ExecuteMsg::UpdateMixnodeConfig { profit_margin_percent, } => try_update_mixnode_config(profit_margin_percent, info, deps), @@ -234,6 +238,14 @@ pub fn try_track_unbond_gateway( Ok(Response::new().add_event(new_track_gateway_unbond_event())) } +pub fn try_compound_operator_reward( + info: MessageInfo, + deps: DepsMut<'_>, +) -> Result { + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_compound_operator_reward(deps.storage) +} + pub fn try_bond_mixnode( mix_node: MixNode, owner_signature: String, @@ -293,6 +305,15 @@ fn try_delegate_to_mixnode( account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage) } +fn try_compound_delegator_reward( + mix_identity: IdentityKey, + info: MessageInfo, + deps: DepsMut<'_>, +) -> Result { + let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + account.try_compound_delegator_reward(mix_identity, deps.storage) +} + fn try_undelegate_from_mixnode( mix_identity: IdentityKey, info: MessageInfo, diff --git a/contracts/vesting/src/traits/bonding_account.rs b/contracts/vesting/src/traits/bonding_account.rs index 4aa88e51a6..ba9a7e2e81 100644 --- a/contracts/vesting/src/traits/bonding_account.rs +++ b/contracts/vesting/src/traits/bonding_account.rs @@ -3,6 +3,11 @@ use cosmwasm_std::{Coin, Env, Response, Storage}; use mixnet_contract_common::{Gateway, MixNode}; pub trait MixnodeBondingAccount { + fn try_compound_operator_reward( + &self, + storage: &dyn Storage, + ) -> Result; + fn try_bond_mixnode( &self, mix_node: MixNode, diff --git a/contracts/vesting/src/traits/delegating_account.rs b/contracts/vesting/src/traits/delegating_account.rs index 166ecb86bb..c3b188a99a 100644 --- a/contracts/vesting/src/traits/delegating_account.rs +++ b/contracts/vesting/src/traits/delegating_account.rs @@ -3,6 +3,12 @@ use cosmwasm_std::{Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::IdentityKey; pub trait DelegatingAccount { + fn try_compound_delegator_reward( + &self, + mix_identity: IdentityKey, + storage: &dyn Storage, + ) -> Result; + fn try_delegate_to_mixnode( &self, mix_identity: IdentityKey, diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index 7507a42fdf..5ba3b16a5b 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -13,6 +13,24 @@ use vesting_contract_common::one_ucoin; use super::Account; impl DelegatingAccount for Account { + fn try_compound_delegator_reward( + &self, + mix_identity: IdentityKey, + storage: &dyn Storage, + ) -> Result { + let msg = MixnetExecuteMsg::CompoundDelegatorRewardOnBehalf { + 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_delegate_to_mixnode( &self, mix_identity: IdentityKey, diff --git a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index ad34223f6d..63d05dff6b 100644 --- a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -14,6 +14,23 @@ use vesting_contract_common::PledgeData; use super::Account; impl MixnodeBondingAccount for Account { + fn try_compound_operator_reward( + &self, + storage: &dyn Storage, + ) -> Result { + let msg = MixnetExecuteMsg::CompoundOperatorRewardOnBehalf { + 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_update_mixnode_config( &self, profit_margin_percent: u8, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index d70913e6aa..6f5a7dee92 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -71,11 +71,12 @@ pub async fn get_reverse_mix_delegations_paged( pub async fn get_delegator_rewards( address: String, mix_identity: IdentityKey, + proxy: Option, state: tauri::State<'_, Arc>>, ) -> Result { Ok( nymd_client!(state) - .get_delegator_rewards(address, mix_identity) + .get_delegator_rewards(address, mix_identity, proxy) .await?, ) } diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 7aa0560775..9c4ab77faf 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -157,6 +157,7 @@ impl Client { &self, address: String, mix_identity: IdentityKey, + proxy: Option, ) -> Result where C: CosmWasmClient + Sync, @@ -164,7 +165,7 @@ impl Client { self.0 .read() .await - .get_delegator_rewards(address, mix_identity) + .get_delegator_rewards(address, mix_identity, proxy) .await }