From 4e47849fc43ad18090203033b71757d639476655 Mon Sep 17 00:00:00 2001 From: Kaicho Sun Date: Wed, 9 Nov 2022 23:58:38 +0800 Subject: [PATCH 1/3] Delegate staking rewards to a beneficiary. --- frame/dapps-staking/src/pallet/mod.rs | 96 ++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/frame/dapps-staking/src/pallet/mod.rs b/frame/dapps-staking/src/pallet/mod.rs index baca18fb..11cef341 100644 --- a/frame/dapps-staking/src/pallet/mod.rs +++ b/frame/dapps-staking/src/pallet/mod.rs @@ -180,6 +180,19 @@ pub mod pallet { ValueQuery, >; + /// Beneficiary of staking rewards on perticular contract. + /// `(staker, contract_id) -> beneficiary_account_id` + #[pallet::storage] + #[pallet::getter(fn staking_beneficiary)] + pub type RewardsBeneficiary = StorageDoubleMap< + _, + Twox64Concat, + T::AccountId, + Blake2_128Concat, + T::SmartContract, + T::AccountId, + >; + /// Stores the current pallet storage version. #[pallet::storage] #[pallet::getter(fn storage_version)] @@ -233,6 +246,12 @@ pub mod pallet { BalanceOf, T::SmartContract, ), + /// Staking rewards beneficiary is set + BeneficiarySet(T::AccountId, T::SmartContract, T::AccountId), + /// Staking rewards beneficiary is removed + BeneficiaryRemoved(T::AccountId, T::SmartContract), + /// Staking rewards beneficiary is updated + BeneficiaryUpdated(T::AccountId, T::SmartContract, T::AccountId), } #[pallet::error] @@ -291,6 +310,10 @@ pub mod pallet { NotActiveStaker, /// Transfering nomination to the same contract NominationTransferToSameContract, + /// There is no beneficiary set for the staker per contract + BeneficiaryNotSet, + /// Beneficiary used is not valid + InvalidBeneficiary, } #[pallet::hooks] @@ -751,7 +774,9 @@ pub mod pallet { staker_info.latest_staked_value(), ); - if should_restake_reward { + let beneficiary = RewardsBeneficiary::::get(&staker, &contract_id).unwrap_or(staker.clone()); + + if staker == beneficiary && should_restake_reward { staker_info .stake(current_era, staker_reward) .map_err(|_| Error::::UnexpectedStakeInfoEra)?; @@ -797,9 +822,9 @@ pub mod pallet { )); } - T::Currency::resolve_creating(&staker, reward_imbalance); + T::Currency::resolve_creating(&beneficiary, reward_imbalance); Self::update_staker_info(&staker, &contract_id, staker_info); - Self::deposit_event(Event::::Reward(staker, contract_id, era, staker_reward)); + Self::deposit_event(Event::::Reward(beneficiary, contract_id, era, staker_reward)); Ok(Some(if should_restake_reward { T::WeightInfo::claim_staker_with_restake() @@ -961,6 +986,71 @@ pub mod pallet { Ok(().into()) } + /// Staker sets the beneficiary of staking rewards. + #[pallet::weight(10_000)] + pub fn set_rewards_beneficiary( + origin: OriginFor, + contract_id: T::SmartContract, + beneficiary: T::AccountId, + ) -> DispatchResultWithPostInfo { + Self::ensure_pallet_enabled()?; + let staker = ensure_signed(origin)?; + + ensure!( + Self::is_active(&contract_id), + Error::::NotOperatedContract + ); + + RewardsBeneficiary::::insert(&staker, &contract_id, &beneficiary); + + Self::deposit_event(Event::::BeneficiarySet(staker, contract_id, beneficiary)); + Ok(().into()) + } + + /// Staker removes the beneficiary of staking rewards. + #[pallet::weight(10_000)] + pub fn remove_rewards_beneficiary( + origin: OriginFor, + contract_id: T::SmartContract, + ) -> DispatchResultWithPostInfo { + Self::ensure_pallet_enabled()?; + let staker = ensure_signed(origin)?; + + RewardsBeneficiary::::remove(&staker, &contract_id); + + Self::deposit_event(Event::::BeneficiaryRemoved(staker, contract_id)); + Ok(().into()) + } + + /// Used by the beneficiary and updates the beneficiary to a new account. + #[pallet::weight(10_000)] + pub fn update_rewards_beneficiary( + origin: OriginFor, + staker: T::AccountId, + contract_id: T::SmartContract, + new_beneficiary: T::AccountId, + ) -> DispatchResultWithPostInfo { + Self::ensure_pallet_enabled()?; + let sender = ensure_signed(origin)?; + + ensure!( + Self::is_active(&contract_id), + Error::::NotOperatedContract + ); + + RewardsBeneficiary::::try_mutate(&staker, &contract_id, |maybe_beneficiary| -> DispatchResultWithPostInfo { + let beneficiary = maybe_beneficiary.as_mut().ok_or(Error::::BeneficiaryNotSet)?; + ensure!(sender == *beneficiary, Error::::InvalidBeneficiary); + + *beneficiary = new_beneficiary.clone(); + + Ok(().into()) + })?; + + Self::deposit_event(Event::::BeneficiaryUpdated(staker, contract_id, new_beneficiary)); + Ok(().into()) + } + /// Used to force set `ContractEraStake` storage values. /// The purpose of this call is only for fixing one of the issues detected with dapps-staking. /// From 665064a072b247dcf8667cf0d7db60e66a5efab2 Mon Sep 17 00:00:00 2001 From: Kaicho Sun Date: Thu, 10 Nov 2022 01:16:03 +0800 Subject: [PATCH 2/3] adding tests --- frame/dapps-staking/src/testing_utils.rs | 25 ++++- frame/dapps-staking/src/tests.rs | 115 +++++++++++++++++++++++ 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/frame/dapps-staking/src/testing_utils.rs b/frame/dapps-staking/src/testing_utils.rs index 88951543..67bb27dc 100644 --- a/frame/dapps-staking/src/testing_utils.rs +++ b/frame/dapps-staking/src/testing_utils.rs @@ -10,6 +10,8 @@ pub(crate) struct MemorySnapshot { staker_info: StakerInfo, contract_info: ContractStakeInfo, free_balance: Balance, + beneficiary: Option, + beneficiary_free_balance: Balance, ledger: AccountLedger, } @@ -27,6 +29,8 @@ impl MemorySnapshot { contract_info: DappsStaking::contract_stake_info(contract_id, era).unwrap_or_default(), ledger: DappsStaking::ledger(&account), free_balance: ::Currency::free_balance(&account), + beneficiary: None, + beneficiary_free_balance: Default::default(), } } @@ -40,8 +44,16 @@ impl MemorySnapshot { contract_info: DappsStaking::contract_stake_info(contract_id, era).unwrap_or_default(), ledger: Default::default(), free_balance: Default::default(), + beneficiary: None, + beneficiary_free_balance: Default::default(), } } + + /// Set beneficiary account + pub(crate) fn set_beneficiary(&mut self, account: AccountId) { + self.beneficiary = Some(account); + self.beneficiary_free_balance = ::Currency::free_balance(&account); + } } /// Used to fetch the free balance of dapps staking account @@ -492,7 +504,9 @@ pub(crate) fn assert_claim_staker(claimer: AccountId, contract_id: &MockSmartCon System::reset_events(); let init_state_claim_era = MemorySnapshot::all(claim_era, contract_id, claimer); - let init_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); + let mut init_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); + let beneficiary = RewardsBeneficiary::::get(claimer, contract_id).unwrap_or(claimer); + init_state_current_era.set_beneficiary(beneficiary); // Calculate contract portion of the reward let (_, stakers_joint_reward) = DappsStaking::dev_stakers_split( @@ -518,7 +532,8 @@ pub(crate) fn assert_claim_staker(claimer: AccountId, contract_id: &MockSmartCon contract_id.clone(), )); - let final_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); + let mut final_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); + final_state_current_era.set_beneficiary(beneficiary); // assert staked and free balances depending on restake check, assert_restake_reward( @@ -545,7 +560,7 @@ pub(crate) fn assert_claim_staker(claimer: AccountId, contract_id: &MockSmartCon // last event should be Reward, regardless of restaking System::assert_last_event(mock::Event::DappsStaking(Event::Reward( - claimer, + beneficiary, contract_id.clone(), claim_era, calculated_reward, @@ -607,8 +622,8 @@ fn assert_restake_reward( } else { // staked values should remain the same, and free balance increase assert_eq!( - init_state_current_era.free_balance + reward, - final_state_current_era.free_balance + init_state_current_era.beneficiary_free_balance + reward, + final_state_current_era.beneficiary_free_balance ); assert_eq!( init_state_current_era.era_info.staked, diff --git a/frame/dapps-staking/src/tests.rs b/frame/dapps-staking/src/tests.rs index 8debe575..9d1ab003 100644 --- a/frame/dapps-staking/src/tests.rs +++ b/frame/dapps-staking/src/tests.rs @@ -1782,6 +1782,32 @@ fn claim_only_payout_is_ok() { }) } +#[test] +fn claim_staker_works_when_beneficiary_set() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let developer = 1; + let staker = 2; + let beneficiary = 3; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + // stake some tokens + let start_era = DappsStaking::current_era(); + assert_register(developer, &contract_id); + let stake_value = 100; + assert_bond_and_stake(staker, &contract_id, stake_value); + let _ = DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary); + + // disable reward restaking + advance_to_era(start_era + 1); + assert_set_reward_destination(staker, RewardDestination::FreeBalance); + + // ensure it's claimed correctly + assert_claim_staker(staker, &contract_id); + }) +} + #[test] fn claim_with_zero_staked_is_ok() { ExternalityBuilder::build().execute_with(|| { @@ -2172,3 +2198,92 @@ pub fn set_contract_stake_info() { ); }) } + +#[test] +pub fn set_rewards_beneficiary_works() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker = 1; + let beneficiary = 2; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + assert_register(10, &contract_id); + + assert_ok!(DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary)); + assert_eq!(RewardsBeneficiary::::get(staker, contract_id), Some(beneficiary)); + }) +} + +#[test] +pub fn set_rewards_beneficiary_failed_when_contract_is_not_registered() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker = 1; + let beneficiary = 2; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + assert_noop!( + DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary), + Error::::NotOperatedContract + ); + }) +} + +#[test] +pub fn remove_rewards_beneficiary_works() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker = 1; + let beneficiary = 2; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + assert_register(10, &contract_id); + let _ = DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary); + + assert_ok!(DappsStaking::remove_rewards_beneficiary(Origin::signed(staker), contract_id)); + assert_eq!(RewardsBeneficiary::::get(staker, contract_id), None); + }) +} + +#[test] +pub fn update_rewards_beneficiary_works() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker = 1; + let beneficiary = 2; + let new_beneficiary = 3; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + assert_register(10, &contract_id); + let _ = DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary); + + assert_ok!(DappsStaking::update_rewards_beneficiary(Origin::signed(beneficiary), staker, contract_id, new_beneficiary)); + assert_eq!(RewardsBeneficiary::::get(staker, contract_id), Some(3)); + }) +} + +#[test] +pub fn update_rewards_beneficiary_failed_when_beneficiary_is_invalid() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker = 1; + let beneficiary = 2; + let new_beneficiary = 3; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + assert_register(10, &contract_id); + let _ = DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary); + + assert_noop!( + DappsStaking::update_rewards_beneficiary(Origin::signed(new_beneficiary), staker, contract_id, new_beneficiary), + Error::::InvalidBeneficiary + ); + assert_eq!(RewardsBeneficiary::::get(staker, contract_id), Some(2)); + }) +} + From 90efe8f44d619cc9fb66d2d1baa8ad70f686b9bd Mon Sep 17 00:00:00 2001 From: Kaicho Sun Date: Thu, 10 Nov 2022 10:47:16 +0800 Subject: [PATCH 3/3] fix claim_staker. --- frame/dapps-staking/src/lib.rs | 5 +- frame/dapps-staking/src/pallet/mod.rs | 61 ++++++----- frame/dapps-staking/src/testing_utils.rs | 123 +++++++++++++---------- frame/dapps-staking/src/tests.rs | 65 +++++++++++- 4 files changed, 174 insertions(+), 80 deletions(-) diff --git a/frame/dapps-staking/src/lib.rs b/frame/dapps-staking/src/lib.rs index 4c939aff..00390baf 100644 --- a/frame/dapps-staking/src/lib.rs +++ b/frame/dapps-staking/src/lib.rs @@ -36,6 +36,9 @@ //! - `maintenance_mode` - enables or disables pallet maintenance mode //! - `set_reward_destination` - sets reward destination for the staker rewards //! - `set_contract_stake_info` - root-only call to set storage value (used for fixing corrupted data) +//! - `set_rewards_beneficiary` - set the beneficary of a staker's rewards +//! - `remove_rewards_beneficiary` - remove the beneficary of a staker's rewards +//! - `update_rewards_beneficiary` - called by the beneficary and update the beneficary of a staker's rewards to a new account //! //! User is encouraged to refer to specific function implementations for more comprehensive documentation. //! @@ -484,7 +487,7 @@ where } } -/// Instruction on how to handle reward payout for stakers. +/// Instruction on how to handle reward payout for stakers when beneficiary is not set. /// In order to make staking more competitive, majority of stakers will want to /// automatically restake anything they earn. #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] diff --git a/frame/dapps-staking/src/pallet/mod.rs b/frame/dapps-staking/src/pallet/mod.rs index 11cef341..07115e80 100644 --- a/frame/dapps-staking/src/pallet/mod.rs +++ b/frame/dapps-staking/src/pallet/mod.rs @@ -312,6 +312,8 @@ pub mod pallet { NominationTransferToSameContract, /// There is no beneficiary set for the staker per contract BeneficiaryNotSet, + /// Not allow non-beneficiary to update + UpdateBeneficiaryNotAllowed, /// Beneficiary used is not valid InvalidBeneficiary, } @@ -732,8 +734,10 @@ pub mod pallet { /// Claim earned staker rewards for the oldest unclaimed era. /// In order to claim multiple eras, this call has to be called multiple times. /// - /// The rewards are always added to the staker's free balance (account) but depending on the reward destination configuration, - /// they might be immediately re-staked. + /// When [`RewardsBeneficiary`] is set, the rewards are added to the beneficiary's free balance and unlocked. + /// When RewardsBeneficiary is not set, the rewards are added to, + /// - staker's free balance but locked with [`RewardDestination`] is StakeBalance + /// - staker's free balance and unlocked with [`RewardDestination`] is FreeBalance #[pallet::weight(T::WeightInfo::claim_staker_with_restake().max(T::WeightInfo::claim_staker_without_restake()))] pub fn claim_staker( origin: OriginFor, @@ -774,9 +778,10 @@ pub mod pallet { staker_info.latest_staked_value(), ); - let beneficiary = RewardsBeneficiary::::get(&staker, &contract_id).unwrap_or(staker.clone()); + let beneficiary = RewardsBeneficiary::::get(&staker, &contract_id); + let mut weight_info = T::WeightInfo::claim_staker_without_restake(); - if staker == beneficiary && should_restake_reward { + if beneficiary.is_none() && should_restake_reward { staker_info .stake(current_era, staker_reward) .map_err(|_| Error::::UnexpectedStakeInfoEra)?; @@ -787,17 +792,7 @@ pub mod pallet { staker_info.len() <= T::MaxEraStakeValues::get(), Error::::TooManyEraStakeValues ); - } - // Withdraw reward funds from the dapps staking pot - let reward_imbalance = T::Currency::withdraw( - &Self::account_id(), - staker_reward, - WithdrawReasons::TRANSFER, - ExistenceRequirement::AllowDeath, - )?; - - if should_restake_reward { ledger.locked = ledger.locked.saturating_add(staker_reward); Self::update_ledger(&staker, ledger); @@ -820,18 +815,24 @@ pub mod pallet { contract_id.clone(), staker_reward, )); + + weight_info = T::WeightInfo::claim_staker_with_restake(); } - T::Currency::resolve_creating(&beneficiary, reward_imbalance); + // Withdraw reward funds from the dapps staking pot + let reward_imbalance = T::Currency::withdraw( + &Self::account_id(), + staker_reward, + WithdrawReasons::TRANSFER, + ExistenceRequirement::AllowDeath, + )?; + + let reward_account = beneficiary.clone().unwrap_or(staker.clone()); + T::Currency::resolve_creating(&reward_account, reward_imbalance); Self::update_staker_info(&staker, &contract_id, staker_info); - Self::deposit_event(Event::::Reward(beneficiary, contract_id, era, staker_reward)); + Self::deposit_event(Event::::Reward(reward_account, contract_id, era, staker_reward)); - Ok(Some(if should_restake_reward { - T::WeightInfo::claim_staker_with_restake() - } else { - T::WeightInfo::claim_staker_without_restake() - }) - .into()) + Ok(Some(weight_info).into()) } /// Claim earned dapp rewards for the specified era. @@ -986,7 +987,9 @@ pub mod pallet { Ok(().into()) } - /// Staker sets the beneficiary of staking rewards. + /// Sets the beneficiary of staking rewards. + /// + /// The dispatch origin is staker. #[pallet::weight(10_000)] pub fn set_rewards_beneficiary( origin: OriginFor, @@ -1000,6 +1003,7 @@ pub mod pallet { Self::is_active(&contract_id), Error::::NotOperatedContract ); + ensure!(beneficiary != staker, Error::::InvalidBeneficiary); RewardsBeneficiary::::insert(&staker, &contract_id, &beneficiary); @@ -1007,7 +1011,9 @@ pub mod pallet { Ok(().into()) } - /// Staker removes the beneficiary of staking rewards. + /// Removes the beneficiary of staking rewards. + /// + /// The dispatch origin is staker. #[pallet::weight(10_000)] pub fn remove_rewards_beneficiary( origin: OriginFor, @@ -1022,7 +1028,9 @@ pub mod pallet { Ok(().into()) } - /// Used by the beneficiary and updates the beneficiary to a new account. + /// Updates the beneficiary to a new account. + /// + /// The dispatch origin is the current beneficiary. #[pallet::weight(10_000)] pub fn update_rewards_beneficiary( origin: OriginFor, @@ -1040,7 +1048,8 @@ pub mod pallet { RewardsBeneficiary::::try_mutate(&staker, &contract_id, |maybe_beneficiary| -> DispatchResultWithPostInfo { let beneficiary = maybe_beneficiary.as_mut().ok_or(Error::::BeneficiaryNotSet)?; - ensure!(sender == *beneficiary, Error::::InvalidBeneficiary); + ensure!(sender == *beneficiary, Error::::UpdateBeneficiaryNotAllowed); + ensure!(new_beneficiary != staker, Error::::InvalidBeneficiary); *beneficiary = new_beneficiary.clone(); diff --git a/frame/dapps-staking/src/testing_utils.rs b/frame/dapps-staking/src/testing_utils.rs index 67bb27dc..89eb6da2 100644 --- a/frame/dapps-staking/src/testing_utils.rs +++ b/frame/dapps-staking/src/testing_utils.rs @@ -29,8 +29,10 @@ impl MemorySnapshot { contract_info: DappsStaking::contract_stake_info(contract_id, era).unwrap_or_default(), ledger: DappsStaking::ledger(&account), free_balance: ::Currency::free_balance(&account), - beneficiary: None, - beneficiary_free_balance: Default::default(), + beneficiary: RewardsBeneficiary::::get(&account, &contract_id), + beneficiary_free_balance: RewardsBeneficiary::::get(&account, &contract_id) + .map(|acc| ::Currency::free_balance(&acc)) + .unwrap_or_default(), } } @@ -48,12 +50,6 @@ impl MemorySnapshot { beneficiary_free_balance: Default::default(), } } - - /// Set beneficiary account - pub(crate) fn set_beneficiary(&mut self, account: AccountId) { - self.beneficiary = Some(account); - self.beneficiary_free_balance = ::Currency::free_balance(&account); - } } /// Used to fetch the free balance of dapps staking account @@ -504,9 +500,7 @@ pub(crate) fn assert_claim_staker(claimer: AccountId, contract_id: &MockSmartCon System::reset_events(); let init_state_claim_era = MemorySnapshot::all(claim_era, contract_id, claimer); - let mut init_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); - let beneficiary = RewardsBeneficiary::::get(claimer, contract_id).unwrap_or(claimer); - init_state_current_era.set_beneficiary(beneficiary); + let init_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); // Calculate contract portion of the reward let (_, stakers_joint_reward) = DappsStaking::dev_stakers_split( @@ -532,8 +526,7 @@ pub(crate) fn assert_claim_staker(claimer: AccountId, contract_id: &MockSmartCon contract_id.clone(), )); - let mut final_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); - final_state_current_era.set_beneficiary(beneficiary); + let final_state_current_era = MemorySnapshot::all(current_era, contract_id, claimer); // assert staked and free balances depending on restake check, assert_restake_reward( @@ -543,7 +536,7 @@ pub(crate) fn assert_claim_staker(claimer: AccountId, contract_id: &MockSmartCon ); // check for stake event if restaking is performed - if DappsStaking::should_restake_reward( + if init_state_current_era.beneficiary.is_none() && DappsStaking::should_restake_reward( init_state_current_era.ledger.reward_destination, init_state_current_era.dapp_info.state, init_state_current_era.staker_info.latest_staked_value(), @@ -559,8 +552,9 @@ pub(crate) fn assert_claim_staker(claimer: AccountId, contract_id: &MockSmartCon } // last event should be Reward, regardless of restaking + let reward_account = init_state_current_era.beneficiary.unwrap_or(claimer); System::assert_last_event(mock::Event::DappsStaking(Event::Reward( - beneficiary, + reward_account, contract_id.clone(), claim_era, calculated_reward, @@ -597,46 +591,71 @@ fn assert_restake_reward( final_state_current_era: &MemorySnapshot, reward: Balance, ) { - if DappsStaking::should_restake_reward( + let beneficiary = final_state_current_era.beneficiary; + let restake = DappsStaking::should_restake_reward( init_state_current_era.ledger.reward_destination, init_state_current_era.dapp_info.state, init_state_current_era.staker_info.latest_staked_value(), - ) { - // staked values should increase - assert_eq!( - init_state_current_era.staker_info.latest_staked_value() + reward, - final_state_current_era.staker_info.latest_staked_value() - ); - assert_eq!( - init_state_current_era.era_info.staked + reward, - final_state_current_era.era_info.staked - ); - assert_eq!( - init_state_current_era.era_info.locked + reward, - final_state_current_era.era_info.locked - ); - assert_eq!( - init_state_current_era.contract_info.total + reward, - final_state_current_era.contract_info.total - ); - } else { - // staked values should remain the same, and free balance increase - assert_eq!( - init_state_current_era.beneficiary_free_balance + reward, - final_state_current_era.beneficiary_free_balance - ); - assert_eq!( - init_state_current_era.era_info.staked, - final_state_current_era.era_info.staked - ); - assert_eq!( - init_state_current_era.era_info.locked, - final_state_current_era.era_info.locked - ); - assert_eq!( - init_state_current_era.contract_info, - final_state_current_era.contract_info - ); + ); + + match (beneficiary, restake) { + (Some(_), _) => { + // staked values should remain the same, and free balance increase + assert_eq!( + init_state_current_era.beneficiary_free_balance + reward, + final_state_current_era.beneficiary_free_balance + ); + assert_eq!( + init_state_current_era.era_info.staked, + final_state_current_era.era_info.staked + ); + assert_eq!( + init_state_current_era.era_info.locked, + final_state_current_era.era_info.locked + ); + assert_eq!( + init_state_current_era.contract_info, + final_state_current_era.contract_info + ); + }, + (None, true) => { + // staked values should increase + assert_eq!( + init_state_current_era.staker_info.latest_staked_value() + reward, + final_state_current_era.staker_info.latest_staked_value() + ); + assert_eq!( + init_state_current_era.era_info.staked + reward, + final_state_current_era.era_info.staked + ); + assert_eq!( + init_state_current_era.era_info.locked + reward, + final_state_current_era.era_info.locked + ); + assert_eq!( + init_state_current_era.contract_info.total + reward, + final_state_current_era.contract_info.total + ); + }, + (None, false) => { + // staked values should remain the same, and the staker's free balance increase + assert_eq!( + init_state_current_era.free_balance + reward, + final_state_current_era.free_balance + ); + assert_eq!( + init_state_current_era.era_info.staked, + final_state_current_era.era_info.staked + ); + assert_eq!( + init_state_current_era.era_info.locked, + final_state_current_era.era_info.locked + ); + assert_eq!( + init_state_current_era.contract_info, + final_state_current_era.contract_info + ); + } } } diff --git a/frame/dapps-staking/src/tests.rs b/frame/dapps-staking/src/tests.rs index 9d1ab003..2d692c53 100644 --- a/frame/dapps-staking/src/tests.rs +++ b/frame/dapps-staking/src/tests.rs @@ -1783,7 +1783,7 @@ fn claim_only_payout_is_ok() { } #[test] -fn claim_staker_works_when_beneficiary_set() { +fn claim_staker_works_when_beneficiary_set_and_reward_destination_is_free_balance() { ExternalityBuilder::build().execute_with(|| { initialize_first_block(); @@ -1808,6 +1808,32 @@ fn claim_staker_works_when_beneficiary_set() { }) } +#[test] +fn claim_staker_works_when_beneficiary_set_and_reward_destination_is_stake_balance() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let developer = 1; + let staker = 2; + let beneficiary = 3; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + // stake some tokens + let start_era = DappsStaking::current_era(); + assert_register(developer, &contract_id); + let stake_value = 100; + assert_bond_and_stake(staker, &contract_id, stake_value); + let _ = DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary); + + // disable reward restaking + advance_to_era(start_era + 1); + assert_set_reward_destination(staker, RewardDestination::StakeBalance); + + // ensure it's claimed correctly + assert_claim_staker(staker, &contract_id); + }) +} + #[test] fn claim_with_zero_staked_is_ok() { ExternalityBuilder::build().execute_with(|| { @@ -2231,6 +2257,23 @@ pub fn set_rewards_beneficiary_failed_when_contract_is_not_registered() { }) } +#[test] +pub fn set_rewards_beneficiary_failed_when_beneficiary_is_staker() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker = 1; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + assert_register(10, &contract_id); + + assert_noop!( + DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, staker), + Error::::InvalidBeneficiary + ); + }) +} + #[test] pub fn remove_rewards_beneficiary_works() { ExternalityBuilder::build().execute_with(|| { @@ -2281,6 +2324,26 @@ pub fn update_rewards_beneficiary_failed_when_beneficiary_is_invalid() { assert_noop!( DappsStaking::update_rewards_beneficiary(Origin::signed(new_beneficiary), staker, contract_id, new_beneficiary), + Error::::UpdateBeneficiaryNotAllowed + ); + assert_eq!(RewardsBeneficiary::::get(staker, contract_id), Some(2)); + }) +} + +#[test] +pub fn update_rewards_beneficiary_failed_when_beneficiary_is_staker() { + ExternalityBuilder::build().execute_with(|| { + initialize_first_block(); + + let staker = 1; + let beneficiary = 2; + let contract_id = MockSmartContract::Evm(H160::repeat_byte(0x01)); + + assert_register(10, &contract_id); + let _ = DappsStaking::set_rewards_beneficiary(Origin::signed(staker), contract_id, beneficiary); + + assert_noop!( + DappsStaking::update_rewards_beneficiary(Origin::signed(beneficiary), staker, contract_id, staker), Error::::InvalidBeneficiary ); assert_eq!(RewardsBeneficiary::::get(staker, contract_id), Some(2));