From f37a415ffb4850dbac47ab163d0e51d6c495ecd3 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 29 Apr 2024 16:33:44 +0200 Subject: [PATCH] Tier config recalculation at the start of each era --- pallets/dapp-staking-v3/src/lib.rs | 13 ++--- pallets/dapp-staking-v3/src/test/mock.rs | 8 ++- pallets/dapp-staking-v3/src/test/tests.rs | 68 ++++++++++++++++++++++- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index cf252d392..e4b43d73e 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -1902,13 +1902,6 @@ pub mod pallet { era_info.migrate_to_next_era(Some(protocol_state.subperiod())); - // Re-calculate tier configuration for the upcoming new period - let tier_params = StaticTierParams::::get(); - let average_price = T::NativePriceProvider::average_price(); - let new_tier_config = - TierConfig::::get().calculate_new(average_price, &tier_params); - TierConfig::::put(new_tier_config); - // Update historical cleanup marker. // Must be called with the new period number. Self::update_cleanup_marker(protocol_state.period_number()); @@ -1958,6 +1951,12 @@ pub mod pallet { } EraRewards::::insert(&era_span_index, span); + // Re-calculate tier configuration for the upcoming new era + let tier_params = StaticTierParams::::get(); + let average_price = T::NativePriceProvider::average_price(); + let new_tier_config = TierConfig::::get().calculate_new(average_price, &tier_params); + TierConfig::::put(new_tier_config); + Self::deposit_event(Event::::NewEra { era: next_era }); if let Some(period_event) = maybe_period_event { Self::deposit_event(period_event); diff --git a/pallets/dapp-staking-v3/src/test/mock.rs b/pallets/dapp-staking-v3/src/test/mock.rs index 788629d9a..bda410e3d 100644 --- a/pallets/dapp-staking-v3/src/test/mock.rs +++ b/pallets/dapp-staking-v3/src/test/mock.rs @@ -108,13 +108,14 @@ impl pallet_balances::Config for Test { pub struct DummyPriceProvider; impl PriceProvider for DummyPriceProvider { fn average_price() -> FixedU128 { - FixedU128::from_rational(1, 10) + NATIVE_PRICE.with(|v| v.borrow().clone()) } } thread_local! { pub(crate) static DOES_PAYOUT_SUCCEED: RefCell = RefCell::new(false); pub(crate) static BLOCK_BEFORE_NEW_ERA: RefCell = RefCell::new(0); + pub(crate) static NATIVE_PRICE: RefCell = RefCell::new(FixedU128::from_rational(1, 100)); } pub struct DummyStakingRewardHandler; @@ -310,7 +311,7 @@ impl ExtBuilder { .unwrap(), }; - // Init tier config, based on the initial params + // Init tier config, based on the initial params. Needs to be adjusted to the init price. let init_tier_config = TiersConfiguration::< ::NumberOfTiers, ::TierSlots, @@ -320,7 +321,8 @@ impl ExtBuilder { reward_portion: tier_params.reward_portion.clone(), tier_thresholds: tier_params.tier_thresholds.clone(), _phantom: Default::default(), - }; + } + .calculate_new(NATIVE_PRICE.with(|v| v.borrow().clone()), &tier_params); pallet_dapp_staking::StaticTierParams::::put(tier_params); pallet_dapp_staking::TierConfig::::put(init_tier_config.clone()); diff --git a/pallets/dapp-staking-v3/src/test/tests.rs b/pallets/dapp-staking-v3/src/test/tests.rs index 6f78d80d4..5e3bff007 100644 --- a/pallets/dapp-staking-v3/src/test/tests.rs +++ b/pallets/dapp-staking-v3/src/test/tests.rs @@ -30,8 +30,9 @@ use frame_support::{ fungible::Unbalanced as FunUnbalanced, Currency, Get, OnFinalize, OnInitialize, ReservableCurrency, }, + BoundedVec, }; -use sp_runtime::traits::Zero; +use sp_runtime::{traits::Zero, FixedU128}; use astar_primitives::{ dapp_staking::{CycleConfiguration, EraNumber, SmartContractHandle}, @@ -2324,11 +2325,72 @@ fn force_with_safeguard_on_fails() { }) } +#[test] +fn tier_config_recalculation_works() { + ExtBuilder::build().execute_with(|| { + let init_price = NATIVE_PRICE.with(|v| v.borrow().clone()); + let init_tier_config = TierConfig::::get(); + + // 1. Advance to a new era, while keeping native price the same. Expect no change in the tier config + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + + assert_eq!( + init_tier_config, + TierConfig::::get(), + "Native price didn't change so tier config should remain the same." + ); + + // 2. Increase the native price, and expect number of tiers to be increased. + NATIVE_PRICE.with(|v| *v.borrow_mut() = init_price * FixedU128::from(3)); + + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + + let new_tier_config = TierConfig::::get(); + assert!( + new_tier_config.number_of_slots > init_tier_config.number_of_slots, + "Price has increased, therefore number of slots must increase." + ); + assert_eq!( + init_tier_config.slots_per_tier.len(), + new_tier_config.slots_per_tier.len(), + "Sanity check." + ); + for idx in 0..init_tier_config.slots_per_tier.len() { + assert!(init_tier_config.slots_per_tier[idx] < new_tier_config.slots_per_tier[idx]); + } + + // 3. Decrease the native price, and expect number of tiers to be increased. + NATIVE_PRICE.with(|v| *v.borrow_mut() = init_price * FixedU128::from_rational(1, 2)); + + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + + let new_tier_config = TierConfig::::get(); + assert!( + new_tier_config.number_of_slots < init_tier_config.number_of_slots, + "Price has decreased, therefore number of slots must decrease." + ); + assert_eq!( + init_tier_config.slots_per_tier.len(), + new_tier_config.slots_per_tier.len(), + "Sanity check." + ); + for idx in 0..init_tier_config.slots_per_tier.len() { + assert!(init_tier_config.slots_per_tier[idx] > new_tier_config.slots_per_tier[idx]); + } + }) +} + #[test] fn get_dapp_tier_assignment_and_rewards_basic_example_works() { ExtBuilder::build().execute_with(|| { - // This test will rely on the configuration inside the mock file. - // If that changes, this test will have to be updated as well. + // Tier config is specially adapter for this test. + TierConfig::::mutate(|config| { + config.number_of_slots = 40; + config.slots_per_tier = BoundedVec::try_from(vec![2, 5, 13, 20]).unwrap(); + }); // Scenario: // - 1st tier is filled up, with one dApp satisfying the threshold but not making it due to lack of tier capacity