diff --git a/pallets/dapp-staking/src/lib.rs b/pallets/dapp-staking/src/lib.rs index c82b163d29..5c21ec210a 100644 --- a/pallets/dapp-staking/src/lib.rs +++ b/pallets/dapp-staking/src/lib.rs @@ -210,6 +210,8 @@ pub mod pallet { /// Tier ranking enabled. #[pallet::constant] type RankingEnabled: Get; + #[pallet::constant] + type MaxBonusMovesPerPeriod: Get; /// Weight info for various calls & operations in the pallet. type WeightInfo: WeightInfo; @@ -316,6 +318,12 @@ pub mod pallet { ExpiredEntriesRemoved { account: T::AccountId, count: u16 }, /// Privileged origin has forced a new era and possibly a subperiod to start from next block. Force { forcing_type: ForcingType }, + StakeMoved { + staker: T::AccountId, + from_contract: T::SmartContract, + to_contract: T::SmartContract, + amount: Balance, + }, } #[pallet::error] @@ -392,6 +400,9 @@ pub mod pallet { NoExpiredEntries, /// Force call is not allowed in production. ForceNotAllowed, + InvalidTargetContract, + InvalidAmount, + NoStakeFound, } /// General information about dApp staking protocol state. @@ -1506,21 +1517,108 @@ pub mod pallet { } /// Used to claim bonus reward for a smart contract on behalf of the specified account, if eligible. - #[pallet::call_index(20)] - #[pallet::weight(T::WeightInfo::claim_bonus_reward())] - pub fn claim_bonus_reward_for( + #[pallet::call_index(21)] + #[pallet::weight(10_000)] + pub fn move_stake( origin: OriginFor, - account: T::AccountId, - smart_contract: T::SmartContract, + from_smart_contract: T::SmartContract, + to_smart_contract: T::SmartContract, + amount: Balance, ) -> DispatchResult { - Self::ensure_pallet_enabled()?; - ensure_signed(origin)?; + let who = ensure_signed(origin)?; - Self::internal_claim_bonus_reward_for(account, smart_contract) + // Ensure contracts are different and registered + ensure!( + from_smart_contract != to_smart_contract, + Error::::InvalidTargetContract + ); + ensure!( + Self::is_registered(&from_smart_contract), + Error::::ContractNotFound + ); + ensure!( + Self::is_registered(&to_smart_contract), + Error::::ContractNotFound + ); + + // Amount validation + ensure!(!amount.is_zero(), Error::::InvalidAmount); + + let protocol_state = ActiveProtocolState::::get(); + let current_era = protocol_state.era; + let period_info = protocol_state.period_info; + + // Reduce stake on source contract + StakerInfo::::try_mutate_exists( + &who, + &from_smart_contract, + |maybe_staker_info| -> DispatchResult { + let mut staker_info = + maybe_staker_info.take().ok_or(Error::::NoStakeFound)?; + ensure!( + staker_info.staked.total() >= amount, + Error::::InsufficientStakeAmount + ); + + staker_info.staked.subtract(amount); + + // Update or remove the staking info + if staker_info.staked.total().is_zero() { + *maybe_staker_info = None; + } else { + *maybe_staker_info = Some(staker_info); + } + Ok(()) + }, + )?; + + // Apply stake to target contract + StakerInfo::::try_mutate( + &who, + &to_smart_contract, + |maybe_staker_info| -> DispatchResult { + match maybe_staker_info { + Some(info) => { + // Add to existing stake + info.staked.add(amount, protocol_state.subperiod()); + info.bonus_status = BonusStatus::SafeMovesRemaining( + T::MaxBonusMovesPerPeriod::get().into(), + ); + } + None => { + // Create new stake entry + let mut new_info = SingularStakingInfo::new( + period_info.number, + protocol_state.subperiod(), + ); + new_info.staked.add(amount, protocol_state.subperiod()); + new_info.bonus_status = BonusStatus::SafeMovesRemaining( + T::MaxBonusMovesPerPeriod::get().into(), + ); + *maybe_staker_info = Some(new_info); + } + } + Ok(()) + }, + )?; + + // Emit event + Self::deposit_event(Event::StakeMoved { + staker: who, + from_contract: from_smart_contract, + to_contract: to_smart_contract, + amount, + }); + + Ok(()) } } impl Pallet { + pub fn is_registered(contract: &T::SmartContract) -> bool { + //TODO: Implement this + true + } /// `true` if the account is a staker, `false` otherwise. pub fn is_staker(account: &T::AccountId) -> bool { Ledger::::contains_key(account) @@ -2150,43 +2248,44 @@ pub mod pallet { .into()) } - /// Internal function that executes the `claim_bonus_reward` logic for the specified account & smart contract. fn internal_claim_bonus_reward_for( account: T::AccountId, smart_contract: T::SmartContract, ) -> DispatchResult { let staker_info = StakerInfo::::get(&account, &smart_contract) .ok_or(Error::::NoClaimableRewards)?; - let protocol_state = ActiveProtocolState::::get(); - // Ensure: - // 1. Period for which rewards are being claimed has ended. - // 2. Account has been a loyal staker. - // 3. Rewards haven't expired. + let protocol_state = ActiveProtocolState::::get(); let staked_period = staker_info.period_number(); - ensure!( - staked_period < protocol_state.period_number(), - Error::::NoClaimableRewards - ); - ensure!( - staker_info.is_loyal(), - Error::::NotEligibleForBonusReward - ); - ensure!( - staker_info.period_number() - >= Self::oldest_claimable_period(protocol_state.period_number()), - Error::::RewardExpired - ); + let oldest_claimable_period = + Self::oldest_claimable_period(protocol_state.period_number()); + + log::debug!("Account: {:?}", account); + log::debug!("Smart Contract: {:?}", smart_contract); + log::debug!("Staked Period: {:?}", staked_period); + log::debug!("Oldest Claimable Period: {:?}", oldest_claimable_period); + + // Check if reward has expired + if staked_period < oldest_claimable_period { + return Err(Error::::RewardExpired.into()); + } + + // Check if loyalty is required and not met + if !staker_info.is_loyal() { + return Err(Error::::NotEligibleForBonusReward.into()); + } let period_end_info = - PeriodEnd::::get(&staked_period).ok_or(Error::::InternalClaimBonusError)?; - // Defensive check - we should never get this far in function if no voting period stake exists. - ensure!( - !period_end_info.total_vp_stake.is_zero(), - Error::::InternalClaimBonusError - ); + PeriodEnd::::get(&staked_period).ok_or(Error::::NoClaimableRewards)?; + log::debug!("Period End Info: {:?}", period_end_info); + + // Ensure rewards are non-zero let eligible_amount = staker_info.staked_amount(Subperiod::Voting); + if period_end_info.total_vp_stake.is_zero() || eligible_amount.is_zero() { + return Err(Error::::NoClaimableRewards.into()); + } + let bonus_reward = Perbill::from_rational(eligible_amount, period_end_info.total_vp_stake) * period_end_info.bonus_reward_pool; @@ -2194,7 +2293,6 @@ pub mod pallet { T::StakingRewardHandler::payout_reward(&account, bonus_reward) .map_err(|_| Error::::RewardPayoutFailed)?; - // Cleanup entry since the reward has been claimed StakerInfo::::remove(&account, &smart_contract); Ledger::::mutate(&account, |ledger| { ledger.contract_stake_count.saturating_dec(); diff --git a/pallets/dapp-staking/src/test/mock.rs b/pallets/dapp-staking/src/test/mock.rs index 1a1b1532cd..9a02ab17a1 100644 --- a/pallets/dapp-staking/src/test/mock.rs +++ b/pallets/dapp-staking/src/test/mock.rs @@ -227,7 +227,9 @@ ord_parameter_types! { pub const ContractUnregisterAccount: AccountId = 1779; pub const ManagerAccount: AccountId = 25711; } - +parameter_types! { + pub const MaxBonusMovesPerPeriod: u8 = 5; +} impl pallet_dapp_staking::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeFreezeReason = RuntimeFreezeReason; @@ -261,6 +263,7 @@ impl pallet_dapp_staking::Config for Test { type WeightInfo = weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = BenchmarkHelper; + type MaxBonusMovesPerPeriod = MaxBonusMovesPerPeriod; } pub struct ExtBuilder {} diff --git a/pallets/dapp-staking/src/test/tests.rs b/pallets/dapp-staking/src/test/tests.rs index c3ffcbe732..f9368c4ef6 100644 --- a/pallets/dapp-staking/src/test/tests.rs +++ b/pallets/dapp-staking/src/test/tests.rs @@ -47,6 +47,8 @@ use astar_primitives::{ }; use std::collections::BTreeMap; +use crate::PeriodEnd; + #[test] fn maintenances_mode_works() { @@ -3422,46 +3424,112 @@ fn claim_staker_rewards_for_basic_example_is_ok() { #[test] fn claim_bonus_reward_for_works() { ExtBuilder::default().build_and_execute(|| { - // Register smart contract, lock&stake some amount + // Register smart contract let dev_account = 1; let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); - + + // Verify starting conditions + let protocol_state = ActiveProtocolState::::get(); + println!("Initial protocol state: {:?}", protocol_state); + assert_eq!( + protocol_state.subperiod(), + Subperiod::Voting, + "Must start in voting period" + ); + + // Lock and stake in first period during voting let staker_account = 2; let lock_amount = 300; assert_lock(staker_account, lock_amount); + + // Verify starting conditions + let protocol_state = ActiveProtocolState::::get(); + println!("Initial protocol state: {:?}", protocol_state); + assert_eq!( + protocol_state.subperiod(), + Subperiod::Voting, + "Must start in voting period" + ); + + // Stake during voting period let stake_amount = 93; assert_stake(staker_account, &smart_contract, stake_amount); + println!("Staked {} in voting period", stake_amount); - // Advance to the next period, and claim the bonus + // Get staking info before period advance + let staking_info = StakerInfo::::get(&staker_account, &smart_contract); + println!("Staking info after stake: {:?}", staking_info); + + // Complete this period advance_to_next_period(); - let claimer_account = 3; + + let protocol_state = ActiveProtocolState::::get(); + println!("Protocol state after period advance: {:?}", protocol_state); + + // Claim regular staking rewards first + for _ in 0..required_number_of_reward_claims(staker_account) { + assert_claim_staker_rewards(staker_account); + } + + // Get staking info before bonus claim + let staking_info = StakerInfo::::get(&staker_account, &smart_contract); + println!("Staking info before bonus claim: {:?}", staking_info); + + // Check if period end info exists + let period_end = PeriodEnd::::get(protocol_state.period_number() - 1); + println!("Period end info: {:?}", period_end); + let (init_staker_balance, init_claimer_balance) = ( Balances::free_balance(&staker_account), - Balances::free_balance(&claimer_account), + Balances::free_balance(&dev_account), + ); + println!("Initial balances - staker: {}, claimer: {}", init_staker_balance, init_claimer_balance); - assert_ok!(DappStaking::claim_bonus_reward_for( - RuntimeOrigin::signed(claimer_account), - staker_account, + // Attempt bonus claim + println!("Attempting bonus claim for period {}", protocol_state.period_number() - 1); + let result = DappStaking::claim_bonus_reward( + RuntimeOrigin::signed(staker_account), smart_contract.clone() - )); - System::assert_last_event(RuntimeEvent::DappStaking(Event::BonusReward { - account: staker_account, - period: ActiveProtocolState::::get().period_number() - 1, - smart_contract, - // for this simple test, entire bonus reward pool goes to the staker - amount: ::StakingRewardHandler::bonus_reward_pool(), - })); - - assert!( - Balances::free_balance(&staker_account) > init_staker_balance, - "Balance must have increased due to the reward payout." ); - assert_eq!( - init_claimer_balance, - Balances::free_balance(&claimer_account), - "Claimer balance must not change since reward is deposited to the staker." + println!("Bonus claim result: {:?}", result); + + assert_ok!(result); + + // Verify double claim fails + assert_noop!( + DappStaking::claim_bonus_reward( + RuntimeOrigin::signed(staker_account), + smart_contract + ), + Error::::NoClaimableRewards ); + + // Verify the event + let events = System::events(); + let bonus_event = events + .iter() + .rev() + .find(|e| matches!(&e.event, RuntimeEvent::DappStaking(Event::BonusReward { .. }))) + .expect("BonusReward event should exist"); + + if let RuntimeEvent::DappStaking(Event::BonusReward { + account, + smart_contract: event_contract, + period, + amount + }) = &bonus_event.event { + assert_eq!(account, &staker_account); + assert_eq!(event_contract, &smart_contract); + assert_eq!(period, &(protocol_state.period_number() - 1)); + + // Verify balances changed correctly + assert_eq!( + Balances::free_balance(&staker_account), + init_staker_balance + amount, + "Staker balance should increase by bonus amount" + ); + } }) -} +} \ No newline at end of file diff --git a/pallets/dapp-staking/src/types.rs b/pallets/dapp-staking/src/types.rs index 8cad803274..e0a61b6801 100644 --- a/pallets/dapp-staking/src/types.rs +++ b/pallets/dapp-staking/src/types.rs @@ -832,7 +832,9 @@ impl Iterator for EraStakePairIter { } /// Describes stake amount in an particular era/period. -#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Debug, PartialEq, Eq, TypeInfo, Default)] +#[derive( + Encode, Decode, MaxEncodedLen, Copy, Clone, Debug, PartialEq, Eq, TypeInfo, Default, PartialOrd, +)] pub struct StakeAmount { /// Amount of staked funds accounting for the voting subperiod. #[codec(compact)] @@ -996,14 +998,25 @@ impl EraInfo { /// Information about how much a particular staker staked on a particular smart contract. /// /// Keeps track of amount staked in the 'voting subperiod', as well as 'build&earn subperiod'. -#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Debug, PartialEq, Eq, TypeInfo, Default)] +#[derive(Encode, Decode, MaxEncodedLen, Clone, Debug, PartialEq, Eq, TypeInfo, Default)] pub struct SingularStakingInfo { - /// Amount staked before, if anything. - pub(crate) previous_staked: StakeAmount, - /// Staked amount - pub(crate) staked: StakeAmount, - /// Indicates whether a staker is a loyal staker or not. - pub(crate) loyal_staker: bool, + pub(crate) previous_staked: StakeAmount, // Amount staked before, if anything. + pub(crate) staked: StakeAmount, // Currently staked amount. + pub(crate) bonus_status: BonusStatus, // New field for tracking bonus status. +} + +#[derive(Encode, Decode, MaxEncodedLen, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] + +pub enum BonusStatus { + SafeMovesRemaining(u32), + NoBonus, + Loyalty, +} + +impl Default for BonusStatus { + fn default() -> Self { + BonusStatus::NoBonus + } } impl SingularStakingInfo { @@ -1020,8 +1033,12 @@ impl SingularStakingInfo { period, ..Default::default() }, - // Loyalty staking is only possible if stake is first made during the voting subperiod. - loyal_staker: subperiod == Subperiod::Voting, + bonus_status: match subperiod { + // Start with safe moves during voting period + Subperiod::Voting => BonusStatus::SafeMovesRemaining(3), // Or whatever initial value + // No bonus during build & earn period + Subperiod::BuildAndEarn => BonusStatus::NoBonus, + }, } } @@ -1060,6 +1077,8 @@ impl SingularStakingInfo { ) -> Vec<(EraNumber, Balance)> { let mut result = Vec::new(); let staked_snapshot = self.staked; + // Track initial voting amount before unstake + let initial_voting = self.staked.voting; // 1. Modify 'current' staked amount, and update the result. self.staked.subtract(amount); @@ -1067,12 +1086,25 @@ impl SingularStakingInfo { self.staked.era = self.staked.era.max(current_era); result.push((self.staked.era, unstaked_amount)); - // 2. Update loyal staker flag accordingly. - self.loyal_staker = self.loyal_staker - && match subperiod { - Subperiod::Voting => !self.staked.voting.is_zero(), - Subperiod::BuildAndEarn => self.staked.voting == staked_snapshot.voting, - }; + // Update bonus status based on refined rules: + // Now using match on copied values to avoid ownership issues + let current_status = self.bonus_status; + self.bonus_status = match (subperiod, current_status) { + // During voting period, maintain loyalty until full unstake + (Subperiod::Voting, status) => { + if self.staked.total().is_zero() { + BonusStatus::NoBonus + } else { + status + } + } + // During B&E period, lose loyalty if voting amount is reduced + (Subperiod::BuildAndEarn, _) if self.staked.voting < initial_voting => { + BonusStatus::NoBonus + } + // Otherwise keep current status + (_, status) => status, + }; // 3. Determine what was the previous staked amount. // This is done by simply comparing where does the _previous era_ fit in the current context. @@ -1147,11 +1179,6 @@ impl SingularStakingInfo { self.staked.for_type(subperiod) } - /// If `true` staker has staked during voting subperiod and has never reduced their sta - pub fn is_loyal(&self) -> bool { - self.loyal_staker - } - /// Period for which this entry is relevant. pub fn period_number(&self) -> PeriodNumber { self.staked.period @@ -1166,6 +1193,9 @@ impl SingularStakingInfo { pub fn is_empty(&self) -> bool { self.staked.is_empty() } + pub fn is_loyal(&self) -> bool { + matches!(self.bonus_status, BonusStatus::SafeMovesRemaining(_)) + } } /// Composite type that holds information about how much was staked on a contract in up to two distinct eras. diff --git a/precompiles/dapp-staking/src/test/mock.rs b/precompiles/dapp-staking/src/test/mock.rs index 04db72aa52..e81eb8ea5d 100644 --- a/precompiles/dapp-staking/src/test/mock.rs +++ b/precompiles/dapp-staking/src/test/mock.rs @@ -253,6 +253,9 @@ impl pallet_dapp_staking::BenchmarkHelper parameter_types! { pub const BaseNativeCurrencyPrice: FixedU128 = FixedU128::from_rational(5, 100); } +parameter_types! { + pub const MaxBonusMovesPerPeriod: u8 = 5; +} impl pallet_dapp_staking::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -282,6 +285,7 @@ impl pallet_dapp_staking::Config for Test { type WeightInfo = pallet_dapp_staking::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = BenchmarkHelper; + type MaxBonusMovesPerPeriod = MaxBonusMovesPerPeriod; } construct_runtime!( diff --git a/precompiles/dapp-staking/src/test/mod.rs b/precompiles/dapp-staking/src/test/mod.rs index c002cf0e6c..07a78ec8e3 100644 --- a/precompiles/dapp-staking/src/test/mod.rs +++ b/precompiles/dapp-staking/src/test/mod.rs @@ -20,3 +20,4 @@ mod mock; mod tests_v2; mod tests_v3; mod types; +pub use mock::*; diff --git a/precompiles/dapp-staking/src/test/tests_v2.rs b/precompiles/dapp-staking/src/test/tests_v2.rs index 5e358f35f7..02da6f2420 100644 --- a/precompiles/dapp-staking/src/test/tests_v2.rs +++ b/precompiles/dapp-staking/src/test/tests_v2.rs @@ -17,6 +17,7 @@ // along with Astar. If not, see . extern crate alloc; +use super::*; use crate::{test::mock::*, *}; use frame_support::assert_ok; use frame_system::RawOrigin; diff --git a/precompiles/dapp-staking/src/test/tests_v3.rs b/precompiles/dapp-staking/src/test/tests_v3.rs index df2f420f5c..f2a3245250 100644 --- a/precompiles/dapp-staking/src/test/tests_v3.rs +++ b/precompiles/dapp-staking/src/test/tests_v3.rs @@ -17,6 +17,7 @@ // along with Astar. If not, see . extern crate alloc; +use super::*; use crate::{test::mock::*, *}; use frame_support::assert_ok; use frame_system::RawOrigin; diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index a0e52da847..2edc23cd14 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -389,6 +389,9 @@ impl DappStakingAccountCheck for AccountCheck { !CollatorSelection::is_account_candidate(account) } } +parameter_types! { + pub const MaxBonusMovesPerPeriod: u8 = 5; +} impl pallet_dapp_staking::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -418,6 +421,7 @@ impl pallet_dapp_staking::Config for Runtime { type WeightInfo = weights::pallet_dapp_staking::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = DAppStakingBenchmarkHelper, AccountId>; + type MaxBonusMovesPerPeriod = MaxBonusMovesPerPeriod; } pub struct InflationPayoutPerBlock; diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 185347545e..7d2318e032 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -462,6 +462,10 @@ parameter_types! { pub const BaseNativeCurrencyPrice: FixedU128 = FixedU128::from_rational(5, 100); } +parameter_types! { + pub const MaxBonusMovesPerPeriod: u8 = 5; +} + impl pallet_dapp_staking::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeFreezeReason = RuntimeFreezeReason; @@ -490,6 +494,7 @@ impl pallet_dapp_staking::Config for Runtime { type WeightInfo = pallet_dapp_staking::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = BenchmarkHelper, AccountId>; + type MaxBonusMovesPerPeriod = MaxBonusMovesPerPeriod; } pub struct InflationPayoutPerBlock; diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index ada15891dd..293ae96f3d 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -460,6 +460,9 @@ parameter_types! { pub const BaseNativeCurrencyPrice: FixedU128 = FixedU128::from_rational(5, 100); } +parameter_types! { + pub const MaxBonusMovesPerPeriod: u8 = 5; +} impl pallet_dapp_staking::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeFreezeReason = RuntimeFreezeReason; @@ -488,6 +491,7 @@ impl pallet_dapp_staking::Config for Runtime { type WeightInfo = weights::pallet_dapp_staking::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = DAppStakingBenchmarkHelper, AccountId>; + type MaxBonusMovesPerPeriod = MaxBonusMovesPerPeriod; } pub struct InflationPayoutPerBlock; diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index 754e752a8e..e14e191284 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -425,6 +425,10 @@ parameter_types! { pub const BaseNativeCurrencyPrice: FixedU128 = FixedU128::from_rational(5, 100); } +parameter_types! { + pub const MaxBonusMovesPerPeriod: u8 = 5; +} + impl pallet_dapp_staking::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeFreezeReason = RuntimeFreezeReason; @@ -453,6 +457,7 @@ impl pallet_dapp_staking::Config for Runtime { type WeightInfo = weights::pallet_dapp_staking::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = DAppStakingBenchmarkHelper, AccountId>; + type MaxBonusMovesPerPeriod = MaxBonusMovesPerPeriod; } pub struct InflationPayoutPerBlock; diff --git a/tests/xcm-simulator/src/mocks/parachain.rs b/tests/xcm-simulator/src/mocks/parachain.rs index 26d2118936..bdf68180a7 100644 --- a/tests/xcm-simulator/src/mocks/parachain.rs +++ b/tests/xcm-simulator/src/mocks/parachain.rs @@ -703,7 +703,9 @@ impl pallet_dapp_staking::BenchmarkHelper parameter_types! { pub const BaseNativeCurrencyPrice: FixedU128 = FixedU128::from_rational(5, 100); } - +parameter_types! { + pub const MaxBonusMovesPerPeriod: u8 = 5; +} impl pallet_dapp_staking::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeFreezeReason = RuntimeFreezeReason; @@ -732,6 +734,7 @@ impl pallet_dapp_staking::Config for Runtime { type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = BenchmarkHelper; + type MaxBonusMovesPerPeriod = MaxBonusMovesPerPeriod; } type Block = frame_system::mocking::MockBlock;