diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/helpers.rs b/common/cosmwasm-smart-contracts/contracts-common/src/helpers.rs new file mode 100644 index 0000000000..4bcc65b75b --- /dev/null +++ b/common/cosmwasm-smart-contracts/contracts-common/src/helpers.rs @@ -0,0 +1,27 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::{BankMsg, Coin, CosmosMsg, Response}; + +pub trait ResponseExt { + fn add_optional_message(self, msg: Option>>) -> Self; + + fn send_tokens(self, to: impl AsRef, amount: Coin) -> Self; +} + +impl ResponseExt for Response { + fn add_optional_message(self, msg: Option>>) -> Self { + if let Some(msg) = msg { + self.add_message(msg) + } else { + self + } + } + + fn send_tokens(self, to: impl AsRef, amount: Coin) -> Self { + self.add_message(BankMsg::Send { + to_address: to.as_ref().to_string(), + amount: vec![amount], + }) + } +} diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs index 301e109d80..a81fafdf41 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs @@ -10,4 +10,6 @@ pub mod events; pub mod signing; pub mod types; +pub mod helpers; + pub use types::*; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs index 6885b8998e..21286924c1 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/nym_node.rs @@ -193,8 +193,6 @@ impl RewardedSetMetadata { } } - // important note: this currently does **NOT** include gateway role as they're not being rewarded - // and the metadata is primarily used for data lookup during epoch transition pub fn highest_rewarded_id(&self) -> NodeId { let mut highest = 0; if self.layer1_metadata.highest_id > highest { @@ -206,6 +204,12 @@ impl RewardedSetMetadata { if self.layer3_metadata.highest_id > highest { highest = self.layer3_metadata.highest_id; } + if self.entry_gateway_metadata.highest_id > highest { + highest = self.entry_gateway_metadata.highest_id; + } + if self.exit_gateway_metadata.highest_id > highest { + highest = self.exit_gateway_metadata.highest_id; + } if self.standby_metadata.highest_id > highest { highest = self.standby_metadata.highest_id; } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index ad71782c44..c2653053da 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -22,6 +22,10 @@ pub struct RoleAssignment { } impl RoleAssignment { + pub fn new(role: Role, nodes: Vec) -> RoleAssignment { + RoleAssignment { role, nodes } + } + pub fn is_final_assignment(&self) -> bool { self.role.is_standby() } diff --git a/contracts/mixnet/src/compat/transactions.rs b/contracts/mixnet/src/compat/transactions.rs index 05153d11ec..64630b564e 100644 --- a/contracts/mixnet/src/compat/transactions.rs +++ b/contracts/mixnet/src/compat/transactions.rs @@ -598,7 +598,7 @@ mod tests { let node_id = test.add_legacy_mixnode("owner2", None); let sender = mock_info("owner2", &[]); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); test.reward_with_distribution(node_id, active_params); @@ -644,7 +644,7 @@ mod tests { let node_id = test.add_dummy_nymnode("owner2", None); let sender = mock_info("owner2", &[]); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); test.reward_with_distribution(node_id, active_params); diff --git a/contracts/mixnet/src/delegations/helpers.rs b/contracts/mixnet/src/delegations/helpers.rs index 257015d5e2..20ae4e8582 100644 --- a/contracts/mixnet/src/delegations/helpers.rs +++ b/contracts/mixnet/src/delegations/helpers.rs @@ -40,7 +40,7 @@ mod tests { test.add_immediate_delegation(delegator, og_amount, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let dist1 = test.reward_with_distribution_ignore_state(mix_id, active_params); test.skip_to_next_epoch_end(); let dist2 = test.reward_with_distribution_ignore_state(mix_id, active_params); diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index da03caaad3..541e7e86c8 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -8,7 +8,6 @@ use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::nodes::helpers::save_new_nymnode_with_id; use crate::nodes::transactions::add_nym_node_inner; use crate::support::helpers::ensure_epoch_in_progress_state; -use crate::support::helpers::AttachSendTokens; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ @@ -16,6 +15,7 @@ use mixnet_contract_common::events::{ }; use mixnet_contract_common::gateway::GatewayConfigUpdate; use mixnet_contract_common::{Gateway, GatewayBondingPayload, NodeCostParams}; +use nym_contracts_common::helpers::ResponseExt; use nym_contracts_common::signing::MessageSignature; pub(crate) fn try_add_gateway( @@ -351,7 +351,7 @@ pub mod tests { assert_eq!(&Addr::unchecked("bob"), first_node.owner()); // add a node owned by fred - let fred_identity = test.add_legacy_gateway("fred", None); + let (fred_identity, _) = test.add_legacy_gateway("fred", None); // let's make sure we now have 2 nodes: let nodes = queries::query_gateways_paged(test.deps(), None, None) diff --git a/contracts/mixnet/src/interval/pending_events.rs b/contracts/mixnet/src/interval/pending_events.rs index ddacc44253..75d78c17b6 100644 --- a/contracts/mixnet/src/interval/pending_events.rs +++ b/contracts/mixnet/src/interval/pending_events.rs @@ -17,6 +17,7 @@ use mixnet_contract_common::pending_events::{ }; use mixnet_contract_common::reward_params::{ActiveSetUpdate, IntervalRewardingParamsUpdate}; use mixnet_contract_common::{BlockHeight, Delegation, NodeId}; +use nym_contracts_common::helpers::ResponseExt; use crate::delegations; use crate::delegations::storage as delegations_storage; @@ -28,7 +29,7 @@ use crate::nodes::helpers::{cleanup_post_unbond_nym_node_storage, get_node_detai use crate::nodes::storage as nymnodes_storage; use crate::rewards::storage as rewards_storage; use crate::rewards::storage::RewardingStorage; -use crate::support::helpers::{ensure_any_node_bonded, AttachSendTokens}; +use crate::support::helpers::ensure_any_node_bonded; pub(crate) trait ContractExecutableEvent { // note: the error only means a HARD error like we failed to read from storage. @@ -837,7 +838,7 @@ mod tests { let active_params = test.active_node_params(100.0); // perform some rewarding here to advance the unit delegation beyond the initial value - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); test.reward_with_distribution_ignore_state(mix_id, active_params); test.skip_to_next_epoch_end(); @@ -911,7 +912,7 @@ mod tests { let active_params = test.active_node_params(100.0); // perform some rewarding here to advance the unit delegation beyond the initial value - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); test.reward_with_distribution_ignore_state(mix_id, active_params); test.skip_to_next_epoch_end(); @@ -1001,7 +1002,7 @@ mod tests { let active_params = test.active_node_params(100.0); // perform some rewarding here to advance the unit delegation beyond the initial value - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); test.reward_with_distribution_ignore_state(mix_id, active_params); test.skip_to_next_epoch_end(); @@ -1135,7 +1136,7 @@ mod tests { .unwrap(); let active_params = test.active_node_params(100.0); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); let dist1 = test.reward_with_distribution_ignore_state(mix_id, active_params); test.skip_to_next_epoch_end(); @@ -1265,7 +1266,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111u128, mix_id_full_pledge); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); let dist1 = test.reward_with_distribution_ignore_state(mix_id_repledge, active_params); let dist2 = @@ -1289,7 +1290,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge]); let dist = test.reward_with_distribution_ignore_state(mix_id_repledge, active_params); @@ -1327,7 +1328,7 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few epochs of rewarding for _ in 0..500 { @@ -1356,7 +1357,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge]); let mut cumulative_op_reward = Decimal::zero(); let mut cumulative_del_reward = Decimal::zero(); @@ -1405,7 +1406,7 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few more epochs of rewarding for _ in 0..500 { @@ -1544,7 +1545,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111u128, mix_id_full_pledge); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); let dist1 = test.reward_with_distribution_ignore_state(mix_id_repledge, active_params); let dist2 = @@ -1568,7 +1569,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge]); let dist = test.reward_with_distribution_ignore_state(mix_id_repledge, active_params); @@ -1606,7 +1607,7 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few epochs of rewarding for _ in 0..500 { @@ -1635,7 +1636,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge]); let mut cumulative_op_reward = Decimal::zero(); let mut cumulative_del_reward = Decimal::zero(); @@ -1684,7 +1685,7 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_mix_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few more epochs of rewarding for _ in 0..500 { diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index ca4c7c0563..c60e4440c0 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -924,7 +924,7 @@ mod tests { let mut test = TestSetup::new(); let rewarding_validator = test.rewarding_validator(); - test.force_change_rewarded_set(vec![1, 2, 3, 4, 5]); + test.force_change_mix_rewarded_set(vec![1, 2, 3, 4, 5]); test.skip_to_current_epoch_end(); let env = test.env(); diff --git a/contracts/mixnet/src/rewards/helpers.rs b/contracts/mixnet/src/rewards/helpers.rs index 793f8b8f14..fba530e334 100644 --- a/contracts/mixnet/src/rewards/helpers.rs +++ b/contracts/mixnet/src/rewards/helpers.rs @@ -269,7 +269,7 @@ mod tests { assert_eq!(res.amount, Uint128::zero()); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let dist1 = test.reward_with_distribution_ignore_state(mix_id, active_params); test.skip_to_next_epoch_end(); @@ -309,7 +309,7 @@ mod tests { assert_eq!(res.amount, Uint128::zero()); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let dist1 = test.reward_with_distribution_ignore_state(mix_id, active_params); test.skip_to_next_epoch_end(); diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index dc296752f1..64f55bcec9 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -316,7 +316,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); let dist = test.reward_with_distribution_ignore_state(mix_id, active_params); @@ -361,7 +361,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); let dist = test.reward_with_distribution_ignore_state(mix_id, active_params); @@ -391,7 +391,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); @@ -469,7 +469,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); let dist = test.reward_with_distribution_ignore_state(mix_id, active_params); @@ -515,7 +515,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); let dist = test.reward_with_distribution_ignore_state(mix_id, active_params); @@ -547,7 +547,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); let dist = test.reward_with_distribution_ignore_state(mix_id, active_params); @@ -585,7 +585,7 @@ mod tests { test.add_immediate_delegation(del2, 150_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); test.reward_with_distribution_ignore_state(mix_id, active_params); @@ -694,7 +694,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let sender = mock_info(owner, &[]); @@ -722,7 +722,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let sender = mock_info(owner, &[]); @@ -748,9 +748,9 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); - test.force_change_rewarded_set(vec![]); + test.force_change_mix_rewarded_set(vec![]); let res = query_estimated_current_epoch_operator_reward( test.deps(), @@ -772,7 +772,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let res = query_estimated_current_epoch_operator_reward( @@ -795,7 +795,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let mix_rewarding = test.mix_rewarding(mix_id); @@ -861,7 +861,7 @@ mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let res = query_estimated_current_epoch_delegator_reward( @@ -887,7 +887,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let sender = mock_info("mix-owner", &[]); @@ -918,7 +918,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let sender = mock_info("mix-owner", &[]); @@ -950,9 +950,9 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); - test.force_change_rewarded_set(vec![]); + test.force_change_mix_rewarded_set(vec![]); let res = query_estimated_current_epoch_delegator_reward( test.deps(), @@ -978,7 +978,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); test.reward_with_distribution_ignore_state(mix_id, active_params); let res = query_estimated_current_epoch_delegator_reward( @@ -1004,7 +1004,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let mix_rewarding = test.mix_rewarding(mix_id); let res = query_estimated_current_epoch_delegator_reward( @@ -1053,7 +1053,7 @@ mod tests { test.add_immediate_delegation(del2, initial_stake2, mix_id); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![mix_id]); + test.force_change_mix_rewarded_set(vec![mix_id]); let params = test.active_node_params(95.0); test.reward_with_distribution_ignore_state(mix_id, params); diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index e3edbcc462..824b918521 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -13,7 +13,6 @@ use crate::rewards::helpers::update_and_save_last_rewarded; use crate::rewards::storage::RewardingStorage; use crate::support::helpers::{ ensure_any_node_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, - AttachSendTokens, }; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; @@ -29,6 +28,7 @@ use mixnet_contract_common::reward_params::{ ActiveSetUpdate, IntervalRewardingParamsUpdate, NodeRewardingParameters, }; use mixnet_contract_common::{Delegation, EpochState, MixNodeDetails, NodeId, NymNodeDetails}; +use nym_contracts_common::helpers::ResponseExt; pub(crate) fn try_reward_node( deps: DepsMut<'_>, @@ -315,10 +315,51 @@ pub mod tests { use crate::mixnodes::storage as mixnodes_storage; use crate::support::tests::fixtures::active_set_update_fixture; use crate::support::tests::test_helpers; + use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::testing::mock_info; + // a simple wrapper to streamline checking for rewarding results + trait TestRewarding { + fn execute_rewarding( + &mut self, + node_id: NodeId, + rewarding_params: NodeRewardingParameters, + ) -> Result; + + fn assert_rewarding( + &mut self, + node_id: NodeId, + rewarding_params: NodeRewardingParameters, + ) -> Response; + } + + impl TestRewarding for TestSetup { + fn execute_rewarding( + &mut self, + node_id: NodeId, + rewarding_params: NodeRewardingParameters, + ) -> Result { + let sender = self.rewarding_validator(); + self.execute_fn( + |deps, env, info| try_reward_node(deps, env, info, node_id, rewarding_params), + sender, + ) + } + + #[track_caller] + fn assert_rewarding( + &mut self, + node_id: NodeId, + rewarding_params: NodeRewardingParameters, + ) -> Response { + let caller = std::panic::Location::caller(); + self.execute_rewarding(node_id, rewarding_params) + .unwrap_or_else(|err| panic!("{caller} failed with: '{err}' ({err:?})")) + } + } + #[cfg(test)] - mod mixnode_rewarding { + mod legacy_mixnode_rewarding { use super::*; use crate::interval::pending_events; use crate::support::tests::test_helpers::{find_attribute, FindAttribute, TestSetup}; @@ -333,46 +374,6 @@ pub mod tests { use mixnet_contract_common::reward_params::WorkFactor; use mixnet_contract_common::EpochStatus; - // a simple wrapper to streamline checking for rewarding results - trait TestRewarding { - fn execute_rewarding( - &mut self, - node_id: NodeId, - rewarding_params: NodeRewardingParameters, - ) -> Result; - - fn assert_rewarding( - &mut self, - node_id: NodeId, - rewarding_params: NodeRewardingParameters, - ) -> Response; - } - - impl TestRewarding for TestSetup { - fn execute_rewarding( - &mut self, - node_id: NodeId, - rewarding_params: NodeRewardingParameters, - ) -> Result { - let sender = self.rewarding_validator(); - self.execute_fn( - |deps, env, info| try_reward_node(deps, env, info, node_id, rewarding_params), - sender, - ) - } - - #[track_caller] - fn assert_rewarding( - &mut self, - node_id: NodeId, - rewarding_params: NodeRewardingParameters, - ) -> Response { - let caller = std::panic::Location::caller(); - self.execute_rewarding(node_id, rewarding_params) - .unwrap_or_else(|err| panic!("{caller} failed with: '{err}' ({err:?})")) - } - } - #[cfg(test)] mod epoch_state_is_correctly_updated { use super::*; @@ -386,7 +387,7 @@ pub mod tests { test.add_rewarded_legacy_mixnode("mix-owner-unbonded-leftover", None); let node_id_never_existed = 42; test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![ + test.force_change_mix_rewarded_set(vec![ node_id_unbonded, node_id_unbonded_leftover, node_id_never_existed, @@ -460,7 +461,7 @@ pub mod tests { let node_id = test.add_rewarded_legacy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); let zero_performance = test.active_node_params(0.); @@ -479,7 +480,7 @@ pub mod tests { let node_id = test.add_rewarded_legacy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); let params = NodeRewardingParameters::new( @@ -501,7 +502,7 @@ pub mod tests { let node_id = test.add_dummy_nymnode("node-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); let zero_performance = test.active_node_params(0.); @@ -520,7 +521,7 @@ pub mod tests { let node_id = test.add_dummy_nymnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); let params = NodeRewardingParameters::new( @@ -542,7 +543,7 @@ pub mod tests { let node_id = test.add_rewarded_legacy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); let active_params = test.active_node_params(100.); @@ -566,7 +567,7 @@ pub mod tests { } test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(ids.clone()); + test.force_change_mix_rewarded_set(ids.clone()); test.start_epoch_transition(); let active_params = test.active_node_params(100.); @@ -611,7 +612,7 @@ pub mod tests { .unwrap(); test.skip_to_current_epoch_end(); - test.force_change_rewarded_set(vec![1, 2, 3]); + test.force_change_mix_rewarded_set(vec![1, 2, 3]); let res = test.execute_rewarding(1, active_params); @@ -633,7 +634,7 @@ pub mod tests { // skip time to when the following epoch is over (since mixnodes are not eligible for rewarding // in the same epoch they're bonded and we need the rewarding epoch to be over) test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); let params = test.legacy_rewarding_params(node_id, 100.); @@ -656,7 +657,7 @@ pub mod tests { let node_id_unbonded_leftover = test.add_rewarded_legacy_mixnode("mix-owner-unbonded-leftover", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![ + test.force_change_mix_rewarded_set(vec![ node_id_unbonded, node_id_unbonded_leftover, node_id_never_existed, @@ -712,7 +713,7 @@ pub mod tests { // node is in the active set BUT the current epoch has just begun test.skip_to_next_epoch(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); let active_params = test.active_node_params(100.); let res = test.execute_rewarding(node_id, active_params); @@ -734,7 +735,7 @@ pub mod tests { let node_id = test.add_rewarded_legacy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id, 42]); + test.force_change_mix_rewarded_set(vec![node_id, 42]); test.start_epoch_transition(); let active_params = test.active_node_params(100.); @@ -762,7 +763,7 @@ pub mod tests { let node_id = test.add_rewarded_legacy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id, 42]); + test.force_change_mix_rewarded_set(vec![node_id, 42]); test.start_epoch_transition(); let zero_perf_params = test.active_node_params(0.); let active_params = test.active_node_params(100.); @@ -800,7 +801,7 @@ pub mod tests { let node_id = test.add_rewarded_legacy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id, 42]); + test.force_change_mix_rewarded_set(vec![node_id, 42]); test.start_epoch_transition(); let zero_work_params = @@ -842,7 +843,7 @@ pub mod tests { let node_id3 = test.add_rewarded_legacy_mixnode("mix-owner3", None); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id1, node_id2, node_id3]); + test.force_change_mix_rewarded_set(vec![node_id1, node_id2, node_id3]); test.start_epoch_transition(); let params = test.active_node_params(98.0); @@ -908,7 +909,7 @@ pub mod tests { test.skip_to_next_epoch_end(); test.start_epoch_transition(); - test.force_change_rewarded_set(vec![node_id1, node_id2, node_id3]); + test.force_change_mix_rewarded_set(vec![node_id1, node_id2, node_id3]); let performance = test_helpers::performance(98.0); test.add_immediate_delegation("delegator1", Uint128::new(100_000_000), node_id2); @@ -981,7 +982,7 @@ pub mod tests { let node_id2 = test.add_rewarded_legacy_mixnode("mix-owner2", Some(operator2)); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id1, node_id2]); + test.force_change_mix_rewarded_set(vec![node_id1, node_id2]); let performance = test_helpers::performance(98.0); test.add_immediate_delegation("delegator1", Uint128::new(100_000_000), node_id1); @@ -1207,12 +1208,305 @@ pub mod tests { } #[cfg(test)] - mod gateway_role_rewarding { + mod legacy_gateway_rewarding { + use super::*; + use crate::support::tests::test_helpers::FindAttribute; + use mixnet_contract_common::events::{BOND_NOT_FOUND_VALUE, NO_REWARD_REASON_KEY}; + use mixnet_contract_common::nym_node::Role; + use mixnet_contract_common::RoleAssignment; + + #[test] + fn regardless_of_performance_or_work_they_get_nothing() { + let mut test = TestSetup::new(); + let (_, node_id) = test.add_legacy_gateway("owner", None); + + test.skip_to_next_epoch_end(); + test.force_assign_rewarded_set(vec![RoleAssignment::new( + Role::EntryGateway, + vec![node_id], + )]); + test.start_epoch_transition(); + + let rewarding_params = test.active_node_params(100.); + let res = test.assert_rewarding(node_id, rewarding_params); + + let reward_attr = res.any_attribute(NO_REWARD_REASON_KEY); + assert_eq!(reward_attr, BOND_NOT_FOUND_VALUE); + + // make sure the epoch actually progressed (i.e. unrewarded gateway hasn't stalled it) + let current = test.current_epoch_state(); + assert_eq!(current, EpochState::ReconcilingEvents) + } + } + + // rewarding for entry gateway, exit gateway and standby nym-nodes + #[cfg(test)] + mod non_legacy_rewarding { use super::*; + use crate::interval::pending_events; + use crate::support::tests::test_helpers::FindAttribute; + use cosmwasm_std::{Decimal, Uint128}; + use mixnet_contract_common::events::{ + BOND_NOT_FOUND_VALUE, NO_REWARD_REASON_KEY, OPERATOR_REWARD_KEY, + ZERO_PERFORMANCE_OR_WORK_VALUE, + }; + use mixnet_contract_common::nym_node::Role; + use mixnet_contract_common::reward_params::WorkFactor; + use mixnet_contract_common::RoleAssignment; + use std::collections::HashMap; + use std::ops::{Deref, DerefMut}; + + struct RewardingSetup { + standby_node: NodeId, + entry_node: NodeId, + exit_node: NodeId, + mixing_node: NodeId, + + inner: TestSetup, + } + + impl RewardingSetup { + pub fn new_rewarding_setup() -> Self { + let mut inner = TestSetup::new(); + let mixing_node = inner.add_dummy_nymnode("mixing-owner", None); + let entry_node = inner.add_dummy_nymnode("entry-owner", None); + let exit_node = inner.add_dummy_nymnode("exit-owner", None); + let standby_node = inner.add_dummy_nymnode("standby-owner", None); + + RewardingSetup { + standby_node, + entry_node, + exit_node, + mixing_node, + inner, + } + } + + pub fn nodes(&self) -> Vec { + vec![ + self.mixing_node, + self.entry_node, + self.exit_node, + self.standby_node, + ] + } + + pub fn reset_rewarded_set(&mut self) { + self.inner.force_assign_rewarded_set(vec![ + RoleAssignment { + role: Role::Layer1, + nodes: vec![self.mixing_node], + }, + RoleAssignment { + role: Role::EntryGateway, + nodes: vec![self.entry_node], + }, + RoleAssignment { + role: Role::ExitGateway, + nodes: vec![self.exit_node], + }, + RoleAssignment { + role: Role::Standby, + nodes: vec![self.standby_node], + }, + ]); + } + + pub fn local_node_role(&self, node_id: NodeId) -> Role { + match node_id { + n if n == self.mixing_node => Role::Layer1, + n if n == self.entry_node => Role::EntryGateway, + n if n == self.exit_node => Role::ExitGateway, + n if n == self.standby_node => Role::Standby, + _ => unreachable!(), + } + } + + pub fn add_to_rewarded_set(&mut self, node_id: NodeId) { + let role = self.local_node_role(node_id); + self.inner.force_assign_rewarded_set(vec![RoleAssignment { + role, + nodes: vec![node_id], + }]) + } + + pub fn reward_all( + &mut self, + performance: f32, + ) -> HashMap> { + let mut results = HashMap::new(); + + self.skip_to_next_epoch_end(); + self.reset_rewarded_set(); + self.start_epoch_transition(); + + let active_params = self.active_node_params(performance); + let standby_params = self.standby_node_params(performance); + + let mixing_node = self.mixing_node; + let entry_node = self.entry_node; + let exit_node = self.exit_node; + let standby_node = self.standby_node; + + results.insert( + mixing_node, + self.execute_rewarding(mixing_node, active_params), + ); + results.insert( + entry_node, + self.execute_rewarding(entry_node, active_params), + ); + results.insert(exit_node, self.execute_rewarding(exit_node, active_params)); + results.insert( + standby_node, + self.execute_rewarding(standby_node, standby_params), + ); + + results + } + } + + impl Deref for RewardingSetup { + type Target = TestSetup; + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + impl DerefMut for RewardingSetup { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + + #[test] + fn when_target_node_has_zero_performance() { + let mut test = RewardingSetup::new_rewarding_setup(); + let results = test.reward_all(0.); + for res in results.into_values() { + let reward_attr = res.unwrap().any_attribute(NO_REWARD_REASON_KEY); + assert_eq!(reward_attr, ZERO_PERFORMANCE_OR_WORK_VALUE); + } + + let current = test.current_epoch_state(); + assert_eq!(current, EpochState::ReconcilingEvents) + } + + #[test] + fn when_target_node_has_zero_work_factor() { + let mut test = RewardingSetup::new_rewarding_setup(); + + test.skip_to_next_epoch_end(); + test.reset_rewarded_set(); + test.start_epoch_transition(); + + let params = + NodeRewardingParameters::new(test_helpers::performance(100.), WorkFactor::zero()); + + for node in test.nodes() { + let res = test.assert_rewarding(node, params); + let reward_attr = res.any_attribute(NO_REWARD_REASON_KEY); + assert_eq!(reward_attr, ZERO_PERFORMANCE_OR_WORK_VALUE); + } + + let current = test.current_epoch_state(); + assert_eq!(current, EpochState::ReconcilingEvents) + } + + #[test] + fn when_theres_only_one_node_to_reward() { + let test_lookup = RewardingSetup::new_rewarding_setup(); + + for node in test_lookup.nodes() { + let mut actual_setup = RewardingSetup::new_rewarding_setup(); + actual_setup.add_to_rewarded_set(node); + let mut res = actual_setup.reward_all(100.); + + // get the response for this particular node + let res = res.remove(&node).unwrap().unwrap(); + let reward: Decimal = res.any_parsed_attribute(OPERATOR_REWARD_KEY); + assert!(!reward.is_zero()); + + let current = actual_setup.current_epoch_state(); + assert_eq!(current, EpochState::ReconcilingEvents) + } + } #[test] - fn unimplemented() { - todo!() + fn when_theres_multiple_nodes_to_reward() { + let mut test = RewardingSetup::new_rewarding_setup(); + let results = test.reward_all(100.); + for res in results.into_values() { + let reward: Decimal = res.unwrap().any_parsed_attribute(OPERATOR_REWARD_KEY); + assert!(!reward.is_zero()); + } + + let current = test.current_epoch_state(); + assert_eq!(current, EpochState::ReconcilingEvents) + } + + #[test] + fn cant_be_performed_for_unbonded_nodes() { + let test_lookup = RewardingSetup::new_rewarding_setup(); + + for node in test_lookup.nodes() { + let mut actual_setup = RewardingSetup::new_rewarding_setup(); + actual_setup.add_to_rewarded_set(node); + + let env = actual_setup.env(); + + // add some delegations to indicate the rewarding information shouldn't get removed + actual_setup.add_immediate_delegation("delegator", Uint128::new(12345678), node); + pending_events::unbond_nym_node(actual_setup.deps_mut(), &env, 123, node).unwrap(); + + let mut res = actual_setup.reward_all(100.); + + // get the response for this particular node + let res = res.remove(&node).unwrap().unwrap(); + let reward_attr = res.any_attribute(NO_REWARD_REASON_KEY); + assert_eq!(reward_attr, BOND_NOT_FOUND_VALUE); + + let current = actual_setup.current_epoch_state(); + assert_eq!(current, EpochState::ReconcilingEvents) + } + } + + #[test] + fn can_only_be_performed_once_per_node_per_epoch() { + let test_lookup = RewardingSetup::new_rewarding_setup(); + + let params = test_lookup.active_node_params(100.0); + for node in test_lookup.nodes() { + let mut actual_setup = RewardingSetup::new_rewarding_setup(); + + actual_setup.skip_to_next_epoch_end(); + + let extra = actual_setup.add_dummy_nymnode("foomp", None); + + // add extra node to the rewarded set so rewarding wouldn't immediately go into event reconciliation + let role = actual_setup.local_node_role(node); + actual_setup + .inner + .force_assign_rewarded_set(vec![RoleAssignment { + role, + nodes: vec![node, extra], + }]); + + actual_setup.start_epoch_transition(); + + // first rewarding + actual_setup.assert_rewarding(node, params); + + // second rewarding + let res = actual_setup.execute_rewarding(node, params).unwrap_err(); + assert_eq!( + res, + MixnetContractError::NodeAlreadyRewarded { + node_id: node, + absolute_epoch_id: 1, + } + ); + } } } @@ -1220,6 +1514,7 @@ pub mod tests { mod withdrawing_delegator_reward { use crate::interval::pending_events; use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup}; + use cosmwasm_std::testing::mock_info; use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128}; use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; @@ -1249,7 +1544,7 @@ pub mod tests { // perform some rewarding so that we'd have non-zero rewards test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id1, node_id2]); + test.force_change_mix_rewarded_set(vec![node_id1, node_id2]); test.start_epoch_transition(); test.reward_with_distribution(node_id1, active_params); test.reward_with_distribution(node_id2, active_params); @@ -1298,7 +1593,7 @@ pub mod tests { // reward mix1, but don't reward mix2 test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id1, low_stake_id]); + test.force_change_mix_rewarded_set(vec![node_id1, low_stake_id]); test.start_epoch_transition(); test.reward_with_distribution(node_id1, active_params); test.reward_with_distribution(low_stake_id, active_params); @@ -1337,7 +1632,7 @@ pub mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id_unbonding, node_id_unbonded_leftover]); + test.force_change_mix_rewarded_set(vec![node_id_unbonding, node_id_unbonded_leftover]); // go through few rewarding cycles before unbonding nodes (partially or fully) for _ in 0..10 { @@ -1416,7 +1711,7 @@ pub mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id_single, node_id_quad]); + test.force_change_mix_rewarded_set(vec![node_id_single, node_id_quad]); // accumulate some rewards let mut accumulated_single = Decimal::zero(); @@ -1577,7 +1872,7 @@ pub mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id]); + test.force_change_mix_rewarded_set(vec![node_id]); test.start_epoch_transition(); test.reward_with_distribution(node_id, active_params); @@ -1606,7 +1901,7 @@ pub mod tests { // reward mix1, but don't reward mix2 test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id1]); + test.force_change_mix_rewarded_set(vec![node_id1]); test.start_epoch_transition(); test.reward_with_distribution(node_id1, active_params); @@ -1639,7 +1934,7 @@ pub mod tests { let active_params = test.active_node_params(100.); test.skip_to_next_epoch_end(); - test.force_change_rewarded_set(vec![node_id_unbonding, node_id_unbonded_leftover]); + test.force_change_mix_rewarded_set(vec![node_id_unbonding, node_id_unbonded_leftover]); // go through few rewarding cycles before unbonding nodes (partially or fully) for _ in 0..10 { diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index 586bd5186e..8997882462 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -7,46 +7,13 @@ use crate::mixnodes::helpers::must_get_mixnode_bond_by_owner; use crate::mixnodes::storage as mixnodes_storage; use crate::nodes::helpers::must_get_node_bond_by_owner; use crate::nodes::storage as nymnodes_storage; -use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage}; +use cosmwasm_std::{Addr, Coin, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::mixnode::PendingMixNodeChanges; use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond, NodeId}; use nym_contracts_common::IdentityKey; use nym_contracts_common::Percent; -// helper trait to attach `Msg` to a response if it's provided -#[allow(dead_code)] -pub(crate) trait AttachOptionalMessage { - fn add_optional_message(self, msg: Option>>) -> Self; -} - -impl AttachOptionalMessage for Response { - fn add_optional_message(self, msg: Option>>) -> Self { - if let Some(msg) = msg { - self.add_message(msg) - } else { - self - } - } -} - -pub(crate) trait AttachSendTokens { - fn send_tokens(self, to: impl AsRef, amount: Coin) -> Self; -} - -impl AttachSendTokens for Response { - fn send_tokens(self, to: impl AsRef, amount: Coin) -> Self { - self.add_message(BankMsg::Send { - to_address: to.as_ref().to_string(), - amount: vec![amount], - }) - } -} - -// pub fn debug_with_visibility>(api: &dyn Api, msg: S) { -// api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into())); -// } - pub(crate) fn validate_pledge( mut pledge: Vec, minimum_pledge: Coin, diff --git a/contracts/mixnet/src/support/tests/legacy.rs b/contracts/mixnet/src/support/tests/legacy.rs index d74808cdf1..2fc8cfe93a 100644 --- a/contracts/mixnet/src/support/tests/legacy.rs +++ b/contracts/mixnet/src/support/tests/legacy.rs @@ -52,7 +52,7 @@ pub(crate) fn save_new_gateway( gateway: Gateway, owner: Addr, pledge: Coin, -) -> Result { +) -> Result<(IdentityKey, NodeId), MixnetContractError> { let gateway_identity = gateway.identity_key.clone(); let bond = GatewayBond::new(pledge.clone(), owner.clone(), env.block.height, gateway); @@ -61,5 +61,5 @@ pub(crate) fn save_new_gateway( let id = next_nymnode_id_counter(storage)?; PREASSIGNED_LEGACY_IDS.save(storage, gateway_identity.clone(), &id)?; - Ok(gateway_identity) + Ok((gateway_identity, id)) } diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 8df5592c17..ba0eecaabb 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -159,6 +159,7 @@ pub mod test_helpers { pub owner: MessageInfo, } + #[allow(unused)] impl TestSetup { pub fn new() -> Self { let deps = init_contract(); @@ -337,6 +338,12 @@ pub mod test_helpers { interval_storage::current_interval(self.deps().storage).unwrap() } + pub fn current_epoch_state(&self) -> EpochState { + interval_storage::current_epoch_status(self.deps().storage) + .unwrap() + .state + } + pub fn active_roles_bucket(&self) -> RoleStorageBucket { ACTIVE_ROLES_BUCKET.load(self.deps().storage).unwrap() } @@ -395,52 +402,54 @@ pub mod test_helpers { .unwrap(); } - pub fn immediately_assign_lowest_mix_layer(&mut self, node_id: NodeId) -> Role { - let active_bucket = ACTIVE_ROLES_BUCKET.load(&self.deps.storage).unwrap(); - - let mut layer1 = read_assigned_roles(&self.deps.storage, Role::Layer1).unwrap(); - let mut layer2 = read_assigned_roles(&self.deps.storage, Role::Layer2).unwrap(); - let mut layer3 = read_assigned_roles(&self.deps.storage, Role::Layer3).unwrap(); + pub fn lowest_mix_layer(&mut self) -> Role { + let layer1 = read_assigned_roles(&self.deps.storage, Role::Layer1).unwrap(); + let layer2 = read_assigned_roles(&self.deps.storage, Role::Layer2).unwrap(); + let layer3 = read_assigned_roles(&self.deps.storage, Role::Layer3).unwrap(); let l1 = layer1.len(); let l2 = layer2.len(); let l3 = layer3.len(); if l1 <= l2 && l1 <= l3 { - layer1.push(node_id); - ROLES - .save( - &mut self.deps.storage, - (active_bucket as u8, Role::Layer1), - &layer1, - ) - .unwrap(); - Role::Layer1 } else if l2 <= l3 && l2 <= l1 { - layer2.push(node_id); - ROLES - .save( - &mut self.deps.storage, - (active_bucket as u8, Role::Layer2), - &layer2, - ) - .unwrap(); - Role::Layer2 } else { - layer3.push(node_id); - ROLES - .save( - &mut self.deps.storage, - (active_bucket as u8, Role::Layer3), - &layer3, - ) - .unwrap(); - Role::Layer3 } } + pub fn immediately_assign_lowest_mix_layer(&mut self, node_id: NodeId) -> Role { + let layer = self.lowest_mix_layer(); + self.immediately_add_to_role(node_id, layer); + layer + } + + pub fn immediately_add_to_role(&mut self, node_id: NodeId, role: Role) { + let active_bucket = ACTIVE_ROLES_BUCKET.load(&self.deps.storage).unwrap(); + let mut current = read_assigned_roles(self.deps().storage, role).unwrap(); + current.push(node_id); + ROLES + .save( + &mut self.deps.storage, + (active_bucket as u8, role), + ¤t, + ) + .unwrap(); + } + + pub fn immediately_assign_standby_role(&mut self, node_id: NodeId) { + self.immediately_add_to_role(node_id, Role::Standby) + } + + pub fn immediately_assign_exit_gateway_role(&mut self, node_id: NodeId) { + self.immediately_add_to_role(node_id, Role::ExitGateway) + } + + pub fn immediately_assign_entry_gateway_role(&mut self, node_id: NodeId) { + self.immediately_add_to_role(node_id, Role::EntryGateway) + } + pub fn add_rewarded_set_nymnode( &mut self, owner: &str, @@ -513,7 +522,7 @@ pub mod test_helpers { ensure_no_existing_bond(&info.sender, &self.deps.storage).unwrap(); signing_storage::increment_signing_nonce(&mut self.deps.storage, info.sender.clone()) .unwrap(); - let node_id = legacy::save_new_mixnode( + legacy::save_new_mixnode( &mut self.deps.storage, env, mixnode, @@ -521,8 +530,38 @@ pub mod test_helpers { info.sender, info.funds[0].clone(), ) - .unwrap(); + .unwrap() + } + pub fn add_rewarded_mixing_node(&mut self, owner: &str, stake: Option) -> NodeId { + let node_id = self.add_dummy_nymnode(owner, stake); + self.immediately_assign_lowest_mix_layer(node_id); + node_id + } + + pub fn add_rewarded_entry_gateway_node( + &mut self, + owner: &str, + stake: Option, + ) -> NodeId { + let node_id = self.add_dummy_nymnode(owner, stake); + self.immediately_assign_entry_gateway_role(node_id); + node_id + } + + pub fn add_rewarded_exit_gateway_node( + &mut self, + owner: &str, + stake: Option, + ) -> NodeId { + let node_id = self.add_dummy_nymnode(owner, stake); + self.immediately_assign_exit_gateway_role(node_id); + node_id + } + + pub fn add_standby_node(&mut self, owner: &str, stake: Option) -> NodeId { + let node_id = self.add_dummy_nymnode(owner, stake); + self.immediately_assign_standby_role(node_id); node_id } @@ -537,7 +576,11 @@ pub mod test_helpers { node_id } - pub fn add_legacy_gateway(&mut self, sender: &str, stake: Option) -> IdentityKey { + pub fn add_legacy_gateway( + &mut self, + sender: &str, + stake: Option, + ) -> (IdentityKey, NodeId) { let stake = self.make_gateway_pledge(stake); let (gateway, _) = self.gateway_with_signature(sender, Some(stake.clone())); @@ -929,7 +972,7 @@ pub mod test_helpers { pub fn reset_role_assignment(&mut self) { let active_bucket = ACTIVE_ROLES_BUCKET.load(&self.deps.storage).unwrap(); - for role in vec![ + for role in [ Role::EntryGateway, Role::ExitGateway, Role::Layer1, @@ -943,34 +986,37 @@ pub mod test_helpers { } } - // note: this does NOT assign gateway role - pub fn force_change_rewarded_set(&mut self, nodes: Vec) { - // reset current assignment + pub fn force_assign_rewarded_set(&mut self, assignment: Vec) { self.reset_role_assignment(); - let mut roles = HashMap::new(); - for node in nodes { - let role = self.immediately_assign_lowest_mix_layer(node); - let assigned = roles.entry(role).or_insert(Vec::new()); - assigned.push(node) - } // we cheat a bit to write to the 'active' bucket instead swap_active_role_bucket(self.deps_mut().storage).unwrap(); + for role_assignment in assignment { + let mut sorted_assignment = role_assignment.clone(); + sorted_assignment.nodes.sort(); - for (role, assignment) in roles { - save_assignment( - self.deps_mut().storage, - RoleAssignment { - role, - nodes: assignment, - }, - ) - .unwrap(); + save_assignment(self.deps_mut().storage, sorted_assignment).unwrap(); } - swap_active_role_bucket(self.deps_mut().storage).unwrap(); } + // note: this does NOT assign gateway role + pub fn force_change_mix_rewarded_set(&mut self, nodes: Vec) { + let mut roles = HashMap::new(); + for node in nodes { + let layer = self.lowest_mix_layer(); + let assigned = roles.entry(layer).or_insert(Vec::new()); + assigned.push(node) + } + + let roles = roles + .into_iter() + .map(|(role, nodes)| RoleAssignment { role, nodes }) + .collect(); + + self.force_assign_rewarded_set(roles) + } + pub fn instantiate_simulator(&self, node: NodeId) -> Simulator { simulator_from_single_node_state(self.deps(), node) } @@ -1266,6 +1312,18 @@ pub mod test_helpers { E: Into>, S: Into; + fn any_attribute(&self, attribute: &str) -> String { + self.attribute::<_, String>(None, attribute) + } + + fn any_parsed_attribute(&self, attribute: &str) -> T + where + T: FromStr, + ::Err: Debug, + { + self.parsed_attribute::<_, String, T>(None, attribute) + } + fn parsed_attribute(&self, event_type: E, attribute: &str) -> T where E: Into>,