diff --git a/Makefile b/Makefile index d1c2520d42..fcd78d82eb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -test-all: test cargo-test-expensive test: build clippy-all cargo-test wasm fmt +test-all: test cargo-test-expensive no-clippy: build cargo-test wasm fmt happy: fmt clippy-happy test clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 224be115d5..471f5cd1d0 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -914,6 +914,29 @@ impl NymdClient { .await } + #[execute("mixnet")] + fn _compound_reward( + &self, + operator: Option, + delegator: Option, + mix_identity: Option, + proxy: Option, + fee: Option, + ) -> (ExecuteMsg, Option) + where + C: SigningCosmWasmClient + Sync, + { + ( + ExecuteMsg::CompoundReward { + operator, + delegator, + mix_identity, + proxy, + }, + fee, + ) + } + #[execute("mixnet")] fn _compound_operator_reward(&self, fee: Option) -> (ExecuteMsg, Option) where diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index e4ef05920b..834f2356a3 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -32,6 +32,12 @@ pub enum ExecuteMsg { CompoundDelegatorReward { mix_identity: IdentityKey, }, + CompoundReward { + operator: Option, + delegator: Option, + mix_identity: Option, + proxy: Option, + }, BondMixnode { mix_node: MixNode, owner_signature: String, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index ce2c894e1b..bdd613edd8 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -107,6 +107,19 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::CompoundReward { + operator, + delegator, + mix_identity, + proxy, + } => crate::rewards::transactions::try_compound_reward( + deps, + env, + operator, + delegator, + mix_identity, + proxy, + ), ExecuteMsg::UpdateRewardingValidatorAddress { address } => { try_update_rewarding_validator_address(deps, info, address) } @@ -275,7 +288,7 @@ pub fn execute( ) } ExecuteMsg::ReconcileDelegations {} => { - crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info) + crate::delegations::transactions::try_reconcile_all_delegation_events(deps) } ExecuteMsg::CheckpointMixnodes {} => { crate::mixnodes::transactions::try_checkpoint_mixnodes( diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index ff39ac9c6f..3b6d5f2502 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -4,7 +4,6 @@ use super::storage::{self, PENDING_DELEGATION_EVENTS}; // use crate::contract::debug_with_visibility; // use crate::contract::debug_with_visibility; use crate::error::ContractError; -use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::storage as mixnodes_storage; use config::defaults::DENOM; use cosmwasm_std::{ @@ -20,16 +19,7 @@ use mixnet_contract_common::{Delegation, IdentityKey}; use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; use vesting_contract_common::one_ucoin; -pub fn try_reconcile_all_delegation_events( - deps: DepsMut<'_>, - info: MessageInfo, -) -> Result { - let state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?; - // check if this is executed by the permitted validator, if not reject the transaction - if info.sender != state.rewarding_validator_address { - return Err(ContractError::Unauthorized); - } - +pub fn try_reconcile_all_delegation_events(deps: DepsMut<'_>) -> Result { _try_reconcile_all_delegation_events(deps.storage, deps.api) } @@ -1063,182 +1053,14 @@ mod tests { #[cfg(test)] mod removing_mix_stake_delegation { use crate::delegations::queries::query_mixnode_delegation; + use super::*; + use crate::support::tests; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_env; use cosmwasm_std::testing::mock_info; use cosmwasm_std::Addr; use cosmwasm_std::Uint128; - use crate::mixnodes::transactions::try_remove_mixnode; - use crate::support::tests; - - use super::storage; - use super::*; - - // TODO: Probably delete due to reconciliation logic - #[ignore] - #[test] - fn fails_if_delegation_never_existed() { - let mut deps = test_helpers::init_contract(); - let env = mock_env(); - let mixnode_owner = "bob"; - let identity = test_helpers::add_mixnode( - mixnode_owner, - tests::fixtures::good_mixnode_pledge(), - deps.as_mut(), - ); - let delegation_owner = Addr::unchecked("sender"); - assert_eq!( - Err(ContractError::NoMixnodeDelegationFound { - identity: identity.clone(), - address: delegation_owner.to_string(), - }), - try_remove_delegation_from_mixnode( - deps.as_mut(), - env, - mock_info(delegation_owner.as_str(), &[]), - identity, - ) - ); - } - - // TODO: Update to work with reconciliation - #[ignore] - #[test] - fn succeeds_if_delegation_existed() { - let mut deps = test_helpers::init_contract(); - let mixnode_owner = "bob"; - let env = mock_env(); - let identity = test_helpers::add_mixnode( - mixnode_owner, - tests::fixtures::good_mixnode_pledge(), - deps.as_mut(), - ); - let delegation_owner = Addr::unchecked("sender"); - try_delegate_to_mixnode( - deps.as_mut(), - mock_env(), - mock_info(delegation_owner.as_str(), &coins(100, DENOM)), - identity.clone(), - ) - .unwrap(); - - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - - let _delegation = query_mixnode_delegation( - &deps.storage, - &deps.api, - identity.clone(), - delegation_owner.clone().into_string(), - None, - ) - .unwrap(); - - let expected_response = Response::new() - .add_message(BankMsg::Send { - to_address: delegation_owner.clone().into(), - amount: coins(100, DENOM), - }) - .add_event(new_undelegation_event( - &delegation_owner, - &None, - &identity, - Uint128::new(100), - )); - - assert_eq!( - Ok(expected_response), - try_remove_delegation_from_mixnode( - deps.as_mut(), - env, - mock_info(delegation_owner.as_str(), &[]), - identity.clone(), - ) - ); - assert!(storage::delegations() - .may_load( - &deps.storage, - (identity.clone(), delegation_owner.as_bytes().to_vec(), 0), - ) - .unwrap() - .is_none()); - - // and total delegation is cleared - assert_eq!( - Uint128::zero(), - mixnodes_storage::TOTAL_DELEGATION - .load(&deps.storage, &identity) - .unwrap() - ) - } - - // TODO: Update to work with reconciliation - #[ignore] - #[test] - fn succeeds_if_delegation_existed_even_if_node_unbonded() { - let mut deps = test_helpers::init_contract(); - let mixnode_owner = "bob"; - let env = mock_env(); - let identity = test_helpers::add_mixnode( - mixnode_owner, - tests::fixtures::good_mixnode_pledge(), - deps.as_mut(), - ); - let delegation_owner = Addr::unchecked("sender"); - try_delegate_to_mixnode( - deps.as_mut(), - mock_env(), - mock_info(delegation_owner.as_str(), &coins(100, DENOM)), - identity.clone(), - ) - .unwrap(); - - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - - let delegation = query_mixnode_delegation( - &deps.storage, - &deps.api, - identity.clone(), - delegation_owner.clone().into_string(), - None, - ) - .unwrap(); - - let expected_response = Response::new() - .add_message(BankMsg::Send { - to_address: delegation_owner.clone().into(), - amount: coins(100, DENOM), - }) - .add_event(new_undelegation_event( - &delegation_owner, - &None, - &identity, - Uint128::new(100), - )); - - try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); - - assert_eq!( - Ok(expected_response), - try_remove_delegation_from_mixnode( - deps.as_mut(), - env, - mock_info(delegation_owner.as_str(), &[]), - identity.clone(), - ) - ); - - _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - - assert!(test_helpers::read_delegation( - &deps.storage, - identity, - delegation_owner.as_bytes(), - mock_env().block.height - ) - .is_none()); - } - #[test] fn total_delegation_is_preserved_if_only_some_undelegate() { let mut deps = test_helpers::init_contract(); diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index 15e082f845..ef94cb5211 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -179,4 +179,6 @@ pub enum ContractError { last_update_time: u64, current_block_time: u64, }, + #[error("`mix_identity` is required when `delegator` is set")] + MissingMixIdentity, } diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 6de369f159..6aa507a1f7 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -370,6 +370,56 @@ pub fn try_compound_delegator_reward_on_behalf( ) } +pub fn try_compound_reward( + deps: DepsMut<'_>, + env: Env, + operator: Option, + delegator: Option, + mix_identity: Option, + proxy: Option, +) -> Result { + let proxy = proxy.and_then(|p| deps.api.addr_validate(&p).ok()); + if let Some(operator_address) = operator { + let operator_address = deps.api.addr_validate(&operator_address)?; + let reward = _try_compound_operator_reward( + deps.storage, + deps.api, + env.block.height, + &operator_address, + proxy, + )?; + Ok( + Response::default().add_event(new_compound_operator_reward_event( + &operator_address, + reward, + )), + ) + } else if let Some(delegator_address) = delegator { + if mix_identity.is_none() { + return Err(ContractError::MissingMixIdentity); + } + + let delegator_address = deps.api.addr_validate(&delegator_address)?; + let reward = _try_compound_delegator_reward( + env.block.height, + deps, + delegator_address.as_str(), + mix_identity.as_ref().unwrap(), + proxy, + )?; + Ok( + Response::default().add_event(new_compound_delegator_reward_event( + &delegator_address, + &None, + reward, + &mix_identity.unwrap(), + )), + ) + } else { + Ok(Response::default()) + } +} + pub fn try_compound_delegator_reward( deps: DepsMut<'_>, env: Env,