diff --git a/pallets/dapp-staking/src/benchmarking/mod.rs b/pallets/dapp-staking/src/benchmarking/mod.rs index 46569ce67..f7b3aee83 100644 --- a/pallets/dapp-staking/src/benchmarking/mod.rs +++ b/pallets/dapp-staking/src/benchmarking/mod.rs @@ -21,7 +21,7 @@ use super::{Pallet as DappStaking, *}; use astar_primitives::Balance; use frame_benchmarking::v2::*; -use frame_support::{assert_ok, migrations::SteppedMigration, weights::WeightMeter}; +use frame_support::assert_ok; use frame_system::{Pallet as System, RawOrigin}; use sp_std::prelude::*; @@ -999,7 +999,7 @@ mod benchmarks { let snapshot_state = ActiveProtocolState::::get(); // Advance over to the last era of the subperiod, and then again to the last block of that era. - advance_to_era::( + force_advance_to_era::( ActiveProtocolState::::get() .period_info .next_subperiod_start_era @@ -1180,73 +1180,6 @@ mod benchmarks { ); } - /// Benchmark a single step of mbm migration. - #[benchmark] - fn step() { - let alice: T::AccountId = account("alice", 0, 1); - - Ledger::::set( - &alice, - AccountLedger { - locked: 1000, - unlocking: vec![ - UnlockingChunk { - amount: 100, - unlock_block: 5, - }, - UnlockingChunk { - amount: 100, - unlock_block: 20, - }, - ] - .try_into() - .unwrap(), - staked: Default::default(), - staked_future: None, - contract_stake_count: 0, - }, - ); - CurrentEraInfo::::put(EraInfo { - total_locked: 1000, - unlocking: 200, - current_stake_amount: Default::default(), - next_stake_amount: Default::default(), - }); - - System::::set_block_number(10u32.into()); - let mut meter = WeightMeter::new(); - - #[block] - { - crate::migration::LazyMigration::>::step( - None, &mut meter, - ) - .unwrap(); - } - - assert_eq!( - Ledger::::get(&alice), - AccountLedger { - locked: 1000, - unlocking: vec![ - UnlockingChunk { - amount: 100, - unlock_block: 5, // already unlocked - }, - UnlockingChunk { - amount: 100, - unlock_block: 30, // double remaining blocks - }, - ] - .try_into() - .unwrap(), - staked: Default::default(), - staked_future: None, - contract_stake_count: 0, - } - ); - } - #[benchmark] fn set_static_tier_params() { initial_config::(); diff --git a/pallets/dapp-staking/src/benchmarking/utils.rs b/pallets/dapp-staking/src/benchmarking/utils.rs index a59861fe7..d4213260d 100644 --- a/pallets/dapp-staking/src/benchmarking/utils.rs +++ b/pallets/dapp-staking/src/benchmarking/utils.rs @@ -18,9 +18,10 @@ use super::{Pallet as DappStaking, *}; -use astar_primitives::{dapp_staking::STANDARD_TIER_SLOTS_ARGS, Balance}; +use astar_primitives::{dapp_staking::FIXED_TIER_SLOTS_ARGS, Balance}; use frame_system::Pallet as System; +use sp_arithmetic::Permill; /// Run to the specified block number. /// Function assumes first block has been initialized. @@ -42,7 +43,7 @@ pub(super) fn run_for_blocks(n: BlockNumberFor) { /// Advance blocks until the specified era has been reached. /// /// Function has no effect if era is already passed. -pub(super) fn advance_to_era(era: EraNumber) { +pub(super) fn _advance_to_era(era: EraNumber) { assert!(era >= ActiveProtocolState::::get().era); while ActiveProtocolState::::get().era < era { run_for_blocks::(One::one()); @@ -65,7 +66,7 @@ pub(super) fn force_advance_to_era(era: EraNumber) { /// Advance blocks until next era has been reached. pub(super) fn _advance_to_next_era() { - advance_to_era::(ActiveProtocolState::::get().era + 1); + _advance_to_era::(ActiveProtocolState::::get().era + 1); } /// Advance to next era, in the next block using the `force` approach. @@ -115,7 +116,7 @@ pub(super) const UNIT: Balance = 1_000_000_000_000_000_000; pub(super) const MIN_TIER_THRESHOLD: Balance = 10 * UNIT; /// Number of slots in the tier system. -pub(super) const NUMBER_OF_SLOTS: u32 = 100; +pub(super) const NUMBER_OF_SLOTS: u32 = 16; /// Random seed. pub(super) const SEED: u32 = 9000; @@ -163,41 +164,37 @@ pub(super) fn initial_config() { pub(super) fn init_tier_settings() { let tier_params = TierParameters:: { reward_portion: BoundedVec::try_from(vec![ - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_percent(70), Permill::from_percent(30), - Permill::from_percent(20), - Permill::from_percent(10), + Permill::from_percent(0), ]) .unwrap(), slot_distribution: BoundedVec::try_from(vec![ - Permill::from_percent(10), - Permill::from_percent(20), - Permill::from_percent(30), - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_parts(375_000), // 37.5% + Permill::from_parts(625_000), // 62.5% + Permill::from_percent(0), ]) .unwrap(), tier_thresholds: BoundedVec::try_from(vec![ - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(11_112_000), // 1.1112% - minimum_required_percentage: Perbill::from_parts(8_889_000), // 0.8889% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(23_200_000), // 2.32% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(5_556_000), // 0.5556% - minimum_required_percentage: Perbill::from_parts(4_400_000), // 0.44% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(9_300_000), // 0.93% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(2_223_000), // 0.2223% - minimum_required_percentage: Perbill::from_parts(2_223_000), // 0.2223% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(3_500_000), // 0.35% }, + // Tier 3: unreachable dummy TierThreshold::FixedPercentage { - required_percentage: Perbill::from_parts(1_667_000), // 0.1667% + required_percentage: Perbill::from_parts(0), // 0% }, ]) .unwrap(), - slot_number_args: STANDARD_TIER_SLOTS_ARGS, + slot_number_args: FIXED_TIER_SLOTS_ARGS, + tier_rank_multipliers: BoundedVec::try_from(vec![0, 24_000, 46_700, 0]).unwrap(), }; let total_issuance = 1000 * MIN_TIER_THRESHOLD; @@ -212,7 +209,7 @@ pub(super) fn init_tier_settings() { // Init tier config, based on the initial params let init_tier_config = TiersConfiguration:: { - slots_per_tier: BoundedVec::try_from(vec![10, 20, 30, 40]).unwrap(), + slots_per_tier: BoundedVec::try_from(vec![0, 6, 10, 0]).unwrap(), reward_portion: tier_params.reward_portion.clone(), tier_thresholds, _phantom: Default::default(), diff --git a/pallets/dapp-staking/src/lib.rs b/pallets/dapp-staking/src/lib.rs index 177eb08c0..eade51c4c 100644 --- a/pallets/dapp-staking/src/lib.rs +++ b/pallets/dapp-staking/src/lib.rs @@ -35,6 +35,10 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + +use alloc::vec; +pub use alloc::vec::Vec; use frame_support::{ pallet_prelude::*, traits::{ @@ -49,13 +53,12 @@ use sp_runtime::{ traits::{One, Saturating, UniqueSaturatedInto, Zero}, Perbill, Permill, SaturatedConversion, }; -pub use sp_std::vec::Vec; use astar_primitives::{ dapp_staking::{ AccountCheck, CycleConfiguration, DAppId, EraNumber, Observer as DAppStakingObserver, PeriodNumber, Rank, RankedTier, SmartContractHandle, StakingRewardHandler, TierId, - TierSlots as TierSlotFunc, STANDARD_TIER_SLOTS_ARGS, + TierSlots as TierSlotFunc, FIXED_TIER_SLOTS_ARGS, }, oracle::PriceProvider, Balance, BlockNumber, @@ -94,7 +97,7 @@ pub mod pallet { use super::*; /// The current storage version. - pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(10); + pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(11); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -183,6 +186,11 @@ pub mod pallet { #[pallet::constant] type MaxNumberOfContracts: Get; + /// Legacy bound for backward compatibility with pre-v11 DAppTierRewards. + // TODO: Can be removed to use MaxNumberOfContracts only after period 4 periods post-revamp-upgrade. + #[pallet::constant] + type MaxNumberOfContractsLegacy: Get; + /// Maximum number of unlocking chunks that can exist per account at a time. #[pallet::constant] type MaxUnlockingChunks: Get; @@ -530,26 +538,45 @@ pub mod pallet { pub slot_number_args: (u64, u64), pub slots_per_tier: Vec, pub safeguard: Option, + pub tier_rank_multipliers: Vec, #[serde(skip)] pub _config: PhantomData, } impl Default for GenesisConfig { fn default() -> Self { - use sp_std::vec; let num_tiers = T::NumberOfTiers::get(); Self { - reward_portion: vec![Permill::from_percent(100 / num_tiers); num_tiers as usize], - slot_distribution: vec![Permill::from_percent(100 / num_tiers); num_tiers as usize], - tier_thresholds: (0..num_tiers) - .rev() - .map(|i| TierThreshold::FixedPercentage { - required_percentage: Perbill::from_percent(i), - }) - .collect(), - slot_number_args: STANDARD_TIER_SLOTS_ARGS, + reward_portion: vec![ + Permill::zero(), // Tier 0: dummy + Permill::from_percent(70), // Tier 1: 70% + Permill::from_percent(30), // Tier 2: 30% + Permill::zero(), // Tier 3: dummy + ], + slot_distribution: vec![ + Permill::zero(), + Permill::from_parts(375_000), // 37.5% + Permill::from_parts(625_000), // 62.5% + Permill::zero(), + ], + tier_thresholds: vec![ + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_percent(3), + }, + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_percent(2), + }, + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_percent(1), + }, + TierThreshold::FixedPercentage { + required_percentage: Perbill::zero(), + }, + ], + slot_number_args: FIXED_TIER_SLOTS_ARGS, slots_per_tier: vec![100; num_tiers as usize], safeguard: None, + tier_rank_multipliers: vec![0u32, 24_000, 46_700, 0], _config: Default::default(), } } @@ -573,6 +600,10 @@ pub mod pallet { ) .expect("Invalid number of tier thresholds provided."), slot_number_args: self.slot_number_args, + tier_rank_multipliers: BoundedVec::::try_from( + self.tier_rank_multipliers.clone(), + ) + .expect("Invalid number of tier points"), }; assert!( tier_params.is_valid(), @@ -1835,21 +1866,23 @@ pub mod pallet { /// /// 2. Sort the entries by the score, in descending order - the top score dApp comes first. /// - /// 3. Calculate rewards for each tier. - /// This is done by dividing the total reward pool into tier reward pools, - /// after which the tier reward pool is divided by the number of available slots in the tier. + /// 3. Assign dApps to tiers based on stake thresholds, calculating ratio-based ranks within each tier. /// - /// 4. Read in tier configuration. This contains information about how many slots per tier there are, - /// as well as the threshold for each tier. Threshold is the minimum amount of stake required to be eligible for a tier. - /// Iterate over tier thresholds & capacities, starting from the top tier, and assign dApps to them. + /// 4. Calculate reward components per tier using the **rank multiplier model**: /// /// ```text - //// for each tier: - /// for each unassigned dApp: - /// if tier has capacity && dApp satisfies the tier threshold: - /// add dapp to the tier - /// else: - /// exit loop since no more dApps will satisfy the threshold since they are sorted by score + /// Step 1: Calculate weights + /// increment = (multiplier - 100%) ÷ MAX_RANK + /// dApp_weight = 100% + rank × increment + /// + /// Step 2: Find reward rate (with overflow cap) + /// target_weight = max_slots × avg_weight (calibrated for avg rank = 5) + /// actual_weight = filled_slots × 100% + ranks_sum × increment + /// reward_per_% = tier_allocation ÷ max(actual_weight, target_weight) + /// + /// Step 3: Pre-compute claim values + /// tier_reward = 100% × reward_per_% + /// rank_reward = increment × reward_per_% /// ``` /// (Sort the entries by dApp ID, in ascending order. This is so we can efficiently search for them using binary search.) /// @@ -1882,6 +1915,7 @@ pub mod pallet { dapp_stakes.sort_unstable_by(|(_, amount_1), (_, amount_2)| amount_2.cmp(amount_1)); let tier_config = TierConfig::::get(); + let tier_params = StaticTierParams::::get(); // In case when tier has 1 more free slot, but two dApps with exactly same score satisfy the threshold, // one of them will be assigned to the tier, and the other one will be assigned to the lower tier, if it exists. @@ -1890,29 +1924,15 @@ pub mod pallet { // There is no guarantee this will persist in the future, so it's best for dApps to do their // best to avoid getting themselves into such situations. - // 3. Calculate rewards. - let tier_rewards = tier_config - .reward_portion - .iter() - .zip(tier_config.slots_per_tier.iter()) - .map(|(percent, slots)| { - if slots.is_zero() { - Zero::zero() - } else { - *percent * dapp_reward_pool / >::into(*slots) - } - }) - .collect::>(); - - // 4. + // 3. // Iterate over configured tier and potential dApps. // Each dApp will be assigned to the best possible tier if it satisfies the required condition, // and tier capacity hasn't been filled yet. let mut dapp_tiers = BTreeMap::new(); - let mut tier_slots = BTreeMap::new(); + let mut tier_rewards = Vec::with_capacity(tier_config.slots_per_tier.len()); + let mut rank_rewards = Vec::with_capacity(tier_config.slots_per_tier.len()); let mut upper_bound = Balance::zero(); - let mut rank_rewards = Vec::new(); for (tier_id, (tier_capacity, lower_bound)) in tier_config .slots_per_tier @@ -1920,6 +1940,8 @@ pub mod pallet { .zip(tier_config.tier_thresholds.iter()) .enumerate() { + let mut tier_slots = BTreeMap::new(); + // Iterate over dApps until one of two conditions has been met: // 1. Tier has no more capacity // 2. dApp doesn't satisfy the tier threshold (since they're sorted, none of the following dApps will satisfy the condition either) @@ -1937,28 +1959,36 @@ pub mod pallet { tier_slots.insert(*dapp_id, RankedTier::new_saturated(tier_id as u8, rank)); } + // 4. Compute tier rewards using rank multiplier + let filled_slots = tier_slots.len() as u32; // sum of all ranks for this tier let ranks_sum = tier_slots .iter() .fold(0u32, |accum, (_, x)| accum.saturating_add(x.rank().into())); - let reward_per_rank = if ranks_sum.is_zero() { - Balance::zero() - } else { - // calculate reward per rank - let tier_reward = tier_rewards.get(tier_id).copied().unwrap_or_default(); - let empty_slots = tier_capacity.saturating_sub(tier_slots.len() as u16); - let remaining_reward = tier_reward.saturating_mul(empty_slots.into()); - // make sure required reward doesn't exceed remaining reward - let reward_per_rank = tier_reward.saturating_div(RankedTier::MAX_RANK.into()); - let expected_reward_for_ranks = - reward_per_rank.saturating_mul(ranks_sum.into()); - let reward_for_ranks = expected_reward_for_ranks.min(remaining_reward); - // re-calculate reward per rank based on available reward - reward_for_ranks.saturating_div(ranks_sum.into()) - }; + let multiplier_bips = tier_params + .tier_rank_multipliers + .get(tier_id) + .copied() + .unwrap_or(10_000); + + let tier_allocation = tier_config + .reward_portion + .get(tier_id) + .copied() + .unwrap_or(Permill::zero()) + * dapp_reward_pool; + + let (tier_reward, rank_reward) = Self::compute_tier_rewards( + tier_allocation, + *tier_capacity, + filled_slots, + ranks_sum, + multiplier_bips, + ); - rank_rewards.push(reward_per_rank); + tier_rewards.push(tier_reward); + rank_rewards.push(rank_reward); dapp_tiers.append(&mut tier_slots); upper_bound = *lower_bound; // current threshold becomes upper bound for next tier } @@ -1967,7 +1997,7 @@ pub mod pallet { // Prepare and return tier & rewards info. // In case rewards creation fails, we just write the default value. This should never happen though. ( - DAppTierRewards::::new( + DAppTierRewards::::new( dapp_tiers, tier_rewards, period, @@ -2441,6 +2471,75 @@ pub mod pallet { Ok(()) } + /// Compute deterministic tier rewards params (base-0 reward and per-rank-step reward) + /// - `tier_base_reward0`: reward for rank 0 (base component) + /// - `reward_per_rank_step`: additional reward per +1 rank + /// `rank10_multiplier_bips` defines the weight at MAX_RANK relative to rank 0 (in bips, 10_000 = 100%) + pub(crate) fn compute_tier_rewards( + tier_allocation: Balance, + tier_capacity: u16, + filled_slots: u32, + ranks_sum: u32, + rank10_multiplier_bips: u32, + ) -> (Balance, Balance) { + const BASE_WEIGHT_BIPS: u128 = 10_000; // 100% in bips + + let max_rank: u128 = RankedTier::MAX_RANK as u128; // 10 + let avg_rank: u128 = max_rank / 2; // 5 + + // If there is nothing to distribute or no participants, return zero components. + if tier_allocation.is_zero() || tier_capacity == 0 || filled_slots == 0 { + return (Balance::zero(), Balance::zero()); + } + + // Convert "Rank 10 earns X% of Rank 0" into a linear per-rank-step extra weight: + // + // step_weight = (weight(MAX_RANK) - weight(0)) / MAX_RANK + // = (rank10_multiplier_bips - BASE_WEIGHT_BIPS) / MAX_RANK + let step_weight_bips: u128 = (rank10_multiplier_bips as u128) + .saturating_sub(BASE_WEIGHT_BIPS) + .saturating_div(max_rank); + + // Total weight contributed by currently occupied slots: + // + // Each slot contributes BASE_WEIGHT_BIPS + // Each rank point contributes step_weight_bips + // + // total_weight = filled_slots * BASE_WEIGHT_BIPS + sum(rank_i) * step_weight_bips + let observed_total_weight: u128 = (filled_slots as u128) + .saturating_mul(BASE_WEIGHT_BIPS) + .saturating_add((ranks_sum as u128).saturating_mul(step_weight_bips)); + + // "Normally filled tier" cap (prevents over-distribution when ranks collide, e.g. all MAX_RANK): + // + // Expected average slot weight if ranks are roughly evenly spread: + // avg_slot_weight = BASE_WEIGHT_BIPS + avg_rank * step_weight_bips + // expected_full_weight = tier_capacity * avg_slot_weight + let avg_slot_weight_bips: u128 = + BASE_WEIGHT_BIPS.saturating_add(avg_rank.saturating_mul(step_weight_bips)); + let expected_full_weight: u128 = + (tier_capacity as u128).saturating_mul(avg_slot_weight_bips); + + // Normalize by the larger of: + // - observed_total_weight: proportional distribution for partially filled tiers + // - expected_full_weight: cap for extreme rank distributions / collisions + let normalization_weight: u128 = observed_total_weight.max(expected_full_weight); + if normalization_weight == 0 { + return (Balance::zero(), Balance::zero()); + } + + let alloc: u128 = tier_allocation.into(); + let tier_base_reward0_u128 = + alloc.saturating_mul(BASE_WEIGHT_BIPS) / normalization_weight; + let reward_per_rank_step_u128 = + alloc.saturating_mul(step_weight_bips) / normalization_weight; + + ( + tier_base_reward0_u128.saturated_into::(), + reward_per_rank_step_u128.saturated_into::(), + ) + } + /// Internal function to transition the dApp staking protocol maintenance mode. /// Ensure this method is **not exposed publicly** and is only used for legitimate maintenance mode transitions invoked by privileged or trusted logic, /// such as `T::ManagerOrigin` or a safe-mode enter/exit notification. diff --git a/pallets/dapp-staking/src/migration.rs b/pallets/dapp-staking/src/migration.rs index 8bd7057b2..400dcdb34 100644 --- a/pallets/dapp-staking/src/migration.rs +++ b/pallets/dapp-staking/src/migration.rs @@ -17,250 +17,421 @@ // along with Astar. If not, see . use super::*; +use astar_primitives::dapp_staking::FIXED_TIER_SLOTS_ARGS; use core::marker::PhantomData; -use frame_support::{ - migration::clear_storage_prefix, - migrations::{MigrationId, SteppedMigration, SteppedMigrationError}, - traits::{GetStorageVersion, OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade}, - weights::WeightMeter, -}; +use frame_support::traits::UncheckedOnRuntimeUpgrade; #[cfg(feature = "try-runtime")] -use sp_std::vec::Vec; +mod try_runtime_imports { + pub use sp_runtime::TryRuntimeError; +} #[cfg(feature = "try-runtime")] -use sp_runtime::TryRuntimeError; +use try_runtime_imports::*; /// Exports for versioned migration `type`s for this pallet. pub mod versioned_migrations { use super::*; - /// Migration V9 to V10 wrapped in a [`frame_support::migrations::VersionedMigration`], ensuring - /// the migration is only performed when on-chain version is 9. - pub type V9ToV10 = frame_support::migrations::VersionedMigration< - 9, - 10, - v10::VersionMigrateV9ToV10, - Pallet, - ::DbWeight, - >; + /// Migration V10 to V11 wrapped in a [`frame_support::migrations::VersionedMigration`], ensuring + /// the migration is only performed when on-chain version is 10. + pub type V10ToV11 = + frame_support::migrations::VersionedMigration< + 10, + 11, + v11::VersionMigrateV10ToV11, + Pallet, + ::DbWeight, + >; } -mod v10 { +/// Configuration for V11 tier parameters +pub trait TierParamsV11Config { + fn reward_portion() -> [Permill; 4]; + fn slot_distribution() -> [Permill; 4]; + fn tier_thresholds() -> [TierThreshold; 4]; + fn slot_number_args() -> (u64, u64); + fn tier_rank_multipliers() -> [u32; 4]; +} + +pub struct DefaultTierParamsV11; +impl TierParamsV11Config for DefaultTierParamsV11 { + fn reward_portion() -> [Permill; 4] { + [ + Permill::from_percent(0), + Permill::from_percent(70), + Permill::from_percent(30), + Permill::from_percent(0), + ] + } + + fn slot_distribution() -> [Permill; 4] { + [ + Permill::from_percent(0), + Permill::from_parts(375_000), // 37.5% + Permill::from_parts(625_000), // 62.5% + Permill::from_percent(0), + ] + } + + fn tier_thresholds() -> [TierThreshold; 4] { + [ + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(23_200_000), // 2.32% + }, + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(9_300_000), // 0.93% + }, + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(3_500_000), // 0.35% + }, + // Tier 3: unreachable dummy + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(0), // 0% + }, + ] + } + + fn slot_number_args() -> (u64, u64) { + FIXED_TIER_SLOTS_ARGS + } + + fn tier_rank_multipliers() -> [u32; 4] { + [0, 24_000, 46_700, 0] + } +} + +mod v11 { use super::*; - use crate::migration::v9::{ - TierParameters as TierParametersV9, TierThreshold as TierThresholdV9, - }; - pub struct VersionMigrateV9ToV10(PhantomData<(T, MaxPercentages)>); + pub struct VersionMigrateV10ToV11( + PhantomData<(T, P, OldErasVoting, OldErasBnE)>, + ); - impl; 4]>> UncheckedOnRuntimeUpgrade - for VersionMigrateV9ToV10 + impl, OldErasBnE: Get> + UncheckedOnRuntimeUpgrade for VersionMigrateV10ToV11 { fn on_runtime_upgrade() -> Weight { - let max_percentages = MaxPercentages::get(); - - // Update static tier parameters with new max thresholds from the runtime configurable param TierThresholds - let result = StaticTierParams::::translate::, _>( - |maybe_old_params| match maybe_old_params { - Some(old_params) => { - let new_tier_thresholds: Vec = old_params - .tier_thresholds - .iter() - .enumerate() - .map(|(idx, old_threshold)| { - let maximum_percentage = if idx < max_percentages.len() { - max_percentages[idx] - } else { - None - }; - map_threshold(old_threshold, maximum_percentage) - }) - .collect(); - - let tier_thresholds = - BoundedVec::::try_from( - new_tier_thresholds, - ); - - match tier_thresholds { - Ok(tier_thresholds) => Some(TierParameters { - slot_distribution: old_params.slot_distribution, - reward_portion: old_params.reward_portion, - tier_thresholds, - slot_number_args: old_params.slot_number_args, - }), - Err(err) => { - log::error!( - "Failed to convert TierThresholds parameters: {:?}", - err - ); - None - } - } - } - _ => None, - }, + let old_eras_voting = OldErasVoting::get(); + let old_eras_bne = OldErasBnE::get(); + + let mut reads: u64 = 0; + let mut writes: u64 = 0; + + // 0. Safety: remove excess dApps if count exceeds new limit + let current_integrated_dapps = IntegratedDApps::::count(); + reads += 1; + + let max_dapps_allowed = T::MaxNumberOfContracts::get(); + + if current_integrated_dapps > max_dapps_allowed { + log::warn!( + target: LOG_TARGET, + "Safety net triggered: {} dApps exceed limit of {}. Removing {} excess dApps.", + current_integrated_dapps, + max_dapps_allowed, + current_integrated_dapps - max_dapps_allowed + ); + + let excess = current_integrated_dapps.saturating_sub(max_dapps_allowed); + let victims: Vec<_> = IntegratedDApps::::iter() + .take(excess as usize) + .map(|(contract, dapp_info)| (contract, dapp_info.id)) + .collect(); + + reads += excess as u64; + + for (contract, dapp_id) in victims { + ContractStake::::remove(&dapp_id); + IntegratedDApps::::remove(&contract); + writes += 2; + + let current_era = ActiveProtocolState::::get().era; + Pallet::::deposit_event(Event::::DAppUnregistered { + smart_contract: contract, + era: current_era, + }); + log::info!( + target: LOG_TARGET, + "Safety net removed dApp ID {} (contract: {:?})", + dapp_id, + core::any::type_name::() + ); + } + + // ActiveProtocolState::get() for era => 1 read (done once for all events) + reads += 1; + } + + // 1. Migrate StaticTierParams + let reward_portion = BoundedVec::::truncate_from( + P::reward_portion().to_vec(), + ); + let slot_distribution = BoundedVec::::truncate_from( + P::slot_distribution().to_vec(), + ); + let tier_thresholds = BoundedVec::::truncate_from( + P::tier_thresholds().to_vec(), + ); + let tier_rank_multipliers = BoundedVec::::truncate_from( + P::tier_rank_multipliers().to_vec(), ); - if result.is_err() { - log::error!("Failed to translate StaticTierParams from previous V9 type to current V10 type. Check TierParametersV9 decoding."); - // Enable maintenance mode. + let new_params = TierParameters:: { + reward_portion, + slot_distribution, + tier_thresholds, + slot_number_args: P::slot_number_args(), + tier_rank_multipliers, + }; + + if !new_params.is_valid() { + log::error!( + target: LOG_TARGET, + "New TierParameters validation failed. Enabling maintenance mode." + ); + + // ActiveProtocolState::mutate => 1 read + 1 write ActiveProtocolState::::mutate(|state| { state.maintenance = true; }); - log::warn!("Maintenance mode enabled."); - return T::DbWeight::get().reads_writes(1, 0); + reads += 1; + writes += 1; + + return T::DbWeight::get().reads_writes(reads, writes); } - T::DbWeight::get().reads_writes(1, 1) + // StaticTierParams::put => 1 write + StaticTierParams::::put(new_params); + writes += 1; + log::info!(target: LOG_TARGET, "StaticTierParams updated successfully"); + + // 2. Update ActiveProtocolState in a SINGLE mutate (avoid extra .get() read) + // ActiveProtocolState::mutate => 1 read + 1 write + ActiveProtocolState::::mutate(|state| { + if state.period_info.subperiod == Subperiod::Voting { + let current_block: u32 = + frame_system::Pallet::::block_number().saturated_into(); + + // Old/new voting period lengths (in blocks) + let old_voting_length: u32 = + old_eras_voting.saturating_mul(T::CycleConfiguration::blocks_per_era()); + let new_voting_length: u32 = Pallet::::blocks_per_voting_period() + .max(T::CycleConfiguration::blocks_per_era()); + + // Old schedule + let remaining_old: u32 = state.next_era_start.saturating_sub(current_block); + let elapsed: u32 = old_voting_length.saturating_sub(remaining_old); + + // New schedule + let remaining_new: u32 = new_voting_length.saturating_sub(elapsed); + + // If new period has already passed (elapsed >= new_voting_length), + // schedule for next block. Otherwise, use the calculated remainder. + state.next_era_start = if remaining_new == 0 { + current_block.saturating_add(1) + } else { + current_block.saturating_add(remaining_new) + }; + + log::info!( + target: LOG_TARGET, + "ActiveProtocolState updated: next_era_start (old_length={}, new_length={}, elapsed={}, remaining_new={})", + old_voting_length, + new_voting_length, + elapsed, + remaining_new + ); + } + if state.period_info.subperiod == Subperiod::Voting { + // Recalculate next_era_start block + let current_block: u32 = + frame_system::Pallet::::block_number().saturated_into(); + let new_voting_length: u32 = Pallet::::blocks_per_voting_period() + .max(T::CycleConfiguration::blocks_per_era()); + let remaining_old: u32 = state.next_era_start.saturating_sub(current_block); + // Carry over remaining time, but never extend beyond the new voting length. + // If already overdue, schedule for the next block. + let remaining_new: u32 = remaining_old.min(new_voting_length).max(1); + + state.next_era_start = current_block.saturating_add(remaining_new); + + log::info!( + target: LOG_TARGET, + "ActiveProtocolState updated: next_era_start (remaining_old={}, remaining_new={})", + remaining_old, + remaining_new + ); + } else { + // Build&Earn: adjust remainder for next_subperiod_start_era + let new_eras_total: EraNumber = + T::CycleConfiguration::eras_per_build_and_earn_subperiod(); + + // "only the remainder" logic + let current_era: EraNumber = state.era; + let old_end: EraNumber = state.period_info.next_subperiod_start_era; + + let remaining_old: EraNumber = old_end.saturating_sub(current_era); + let elapsed: EraNumber = old_eras_bne.saturating_sub(remaining_old); + + let remaining_new: EraNumber = new_eras_total.saturating_sub(elapsed); + + state.period_info.next_subperiod_start_era = + current_era.saturating_add(remaining_new); + + log::info!( + target: LOG_TARGET, + "ActiveProtocolState updated: next_subperiod_start_era (remainder-adjusted)" + ); + } + }); + reads += 1; + writes += 1; + + T::DbWeight::get().reads_writes(reads, writes) } #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { - let old_params = v9::StaticTierParams::::get().ok_or_else(|| { - TryRuntimeError::Other( - "dapp-staking-v3::migration::v10: No old params found for StaticTierParams", - ) - })?; - Ok(old_params.encode()) + let protocol_state = ActiveProtocolState::::get(); + let current_block: u32 = frame_system::Pallet::::block_number().saturated_into(); + + let pre_dapp_count = IntegratedDApps::::count(); + let max_allowed = T::MaxNumberOfContracts::get(); + + log::info!( + target: LOG_TARGET, + "Pre-upgrade: dApp count={}, max={}, cleanup_needed={}", + pre_dapp_count, + max_allowed, + pre_dapp_count > max_allowed + ); + + Ok(( + protocol_state.period_info.subperiod, + protocol_state.era, + protocol_state.next_era_start, + protocol_state.period_info.next_subperiod_start_era, + current_block, + pre_dapp_count, + max_allowed, + ) + .encode()) } #[cfg(feature = "try-runtime")] fn post_upgrade(data: Vec) -> Result<(), TryRuntimeError> { - // Decode the old values - let old_params: TierParametersV9 = Decode::decode(&mut &data[..]) - .map_err(|_| { - TryRuntimeError::Other( - "dapp-staking-v3::migration::v10: Failed to decode old values", - ) - })?; - - // Get the new values - let new_config = TierConfig::::get(); - let new_params = StaticTierParams::::get(); - - // Verify that new params and new config are valid - assert!(new_params.is_valid()); - assert!(new_config.is_valid()); - - // Verify parameters remain unchanged - assert_eq!( - old_params.slot_distribution, new_params.slot_distribution, - "dapp-staking-v3::migration::v10: Slot distribution has changed" + let ( + old_subperiod, + old_era, + old_next_era_start, + old_next_subperiod_era, + pre_block, + pre_dapp_count, + max_allowed, + ): (Subperiod, EraNumber, u32, EraNumber, u32, u32, u32) = + Decode::decode(&mut &data[..]) + .map_err(|_| TryRuntimeError::Other("Failed to decode pre-upgrade data"))?; + let old_eras_voting = OldErasVoting::get(); + let old_eras_bne = OldErasBnE::get(); + + // Verify storage version + ensure!( + Pallet::::on_chain_storage_version() == StorageVersion::new(11), + "Storage version should be 11" ); - assert_eq!( - old_params.reward_portion, new_params.reward_portion, - "dapp-staking-v3::migration::v10: Reward portion has changed" + + // 1. Verify cleanup worked + let post_dapp_count = IntegratedDApps::::count(); + log::debug!( + "post_dapp_count={}, max_allowed={}", + post_dapp_count, + max_allowed ); - assert_eq!( - old_params.tier_thresholds.len(), - new_params.tier_thresholds.len(), - "dapp-staking-v3::migration::v10: Number of tier thresholds has changed" + ensure!( + post_dapp_count <= max_allowed, + "dApp count still exceeds limit", ); - for (_, (old_threshold, new_threshold)) in old_params - .tier_thresholds - .iter() - .zip(new_params.tier_thresholds.iter()) - .enumerate() - { - match (old_threshold, new_threshold) { - ( - TierThresholdV9::FixedPercentage { - required_percentage: old_req, - }, - TierThreshold::FixedPercentage { - required_percentage: new_req, - }, - ) => { - assert_eq!( - old_req, new_req, - "dapp-staking-v3::migration::v10: Fixed percentage changed", - ); - } - ( - TierThresholdV9::DynamicPercentage { - percentage: old_percentage, - minimum_required_percentage: old_min, - }, - TierThreshold::DynamicPercentage { - percentage: new_percentage, - minimum_required_percentage: new_min, - maximum_possible_percentage: _, // We don't verify this as it's new - }, - ) => { - assert_eq!( - old_percentage, new_percentage, - "dapp-staking-v3::migration::v10: Percentage changed" - ); - assert_eq!( - old_min, new_min, - "dapp-staking-v3::migration::v10: Minimum percentage changed" - ); - } - _ => { - return Err(TryRuntimeError::Other( - "dapp-staking-v3::migration::v10: Tier threshold type mismatch", - )); - } - } - } - - let expected_max_percentages = MaxPercentages::get(); - for (idx, tier_threshold) in new_params.tier_thresholds.iter().enumerate() { - if let TierThreshold::DynamicPercentage { - maximum_possible_percentage, - .. - } = tier_threshold - { - let expected_maximum_percentage = if idx < expected_max_percentages.len() { - expected_max_percentages[idx] - } else { - None - } - .unwrap_or(Perbill::from_percent(100)); - assert_eq!( - *maximum_possible_percentage, expected_maximum_percentage, - "dapp-staking-v3::migration::v10: Max percentage differs from expected", - ); - } + if pre_dapp_count > max_allowed { + let expected_removed = pre_dapp_count - max_allowed; + let actual_removed = pre_dapp_count - post_dapp_count; + log::debug!( + "Removed {} dApps, expected to remove {}", + actual_removed, + expected_removed + ); + ensure!( + actual_removed == expected_removed, + "Mismatch in the expected dApps to be unregistered" + ); } - // Verify storage version has been updated + // 2. Verify new StaticTierParams are valid + let new_params = StaticTierParams::::get(); + ensure!(new_params.is_valid(), "New tier params invalid"); ensure!( - Pallet::::on_chain_storage_version() >= 10, - "dapp-staking-v3::migration::v10: Wrong storage version." + new_params.reward_portion.as_slice() == P::reward_portion(), + "reward_portion mismatch" + ); + ensure!( + new_params.tier_rank_multipliers.as_slice() == P::tier_rank_multipliers(), + "tier_rank_multipliers mismatch" ); - Ok(()) - } - } + // 3. Verify ActiveProtocolState update + let protocol_state = ActiveProtocolState::::get(); + ensure!(!protocol_state.maintenance, "Maintenance mode enabled"); + + if old_subperiod == Subperiod::Voting { + let old_voting_length: u32 = + old_eras_voting.saturating_mul(T::CycleConfiguration::blocks_per_era()); + let new_voting_length: u32 = Pallet::::blocks_per_voting_period(); + + let remaining_old: u32 = old_next_era_start.saturating_sub(pre_block); + let elapsed: u32 = old_voting_length.saturating_sub(remaining_old); + let remaining_new: u32 = new_voting_length.saturating_sub(elapsed); + + let expected = if remaining_new == 0 { + pre_block.saturating_add(1) + } else { + pre_block.saturating_add(remaining_new) + }; + + ensure!( + protocol_state.next_era_start == expected, + "Voting: next_era_start incorrect" + ); + } else { + let new_total: EraNumber = + T::CycleConfiguration::eras_per_build_and_earn_subperiod(); + let remaining_old: EraNumber = old_next_subperiod_era.saturating_sub(old_era); + let elapsed: EraNumber = old_eras_bne.saturating_sub(remaining_old); + let remaining_new: EraNumber = new_total.saturating_sub(elapsed); + let expected: EraNumber = old_era.saturating_add(remaining_new); + + ensure!( + protocol_state.period_info.next_subperiod_start_era == expected, + "BuildEarn: next_subperiod_start_era incorrect" + ); + ensure!( + old_next_era_start > expected, + "next_era_start did not update as expected" + ); + } - pub fn map_threshold(old: &TierThresholdV9, max_percentage: Option) -> TierThreshold { - match old { - TierThresholdV9::FixedPercentage { - required_percentage, - } => TierThreshold::FixedPercentage { - required_percentage: *required_percentage, - }, - TierThresholdV9::DynamicPercentage { - percentage, - minimum_required_percentage, - } => TierThreshold::DynamicPercentage { - percentage: *percentage, - minimum_required_percentage: *minimum_required_percentage, - maximum_possible_percentage: max_percentage.unwrap_or(Perbill::from_percent(100)), // Default to 100% if not specified, - }, + log::info!(target: LOG_TARGET, "Post-upgrade: All checks passed"); + Ok(()) } } } -mod v9 { +mod v10 { use super::*; use frame_support::storage_alias; - #[derive(Encode, Decode)] + /// v10 TierParameters (without tier_rank_multipliers) + #[derive(Encode, Decode, Clone)] pub struct TierParameters> { pub reward_portion: BoundedVec, pub slot_distribution: BoundedVec, @@ -268,151 +439,7 @@ mod v9 { pub slot_number_args: (u64, u64), } - #[derive(Encode, Decode)] - pub enum TierThreshold { - FixedPercentage { - required_percentage: Perbill, - }, - DynamicPercentage { - percentage: Perbill, - minimum_required_percentage: Perbill, - }, - } - - /// v9 type for [`crate::StaticTierParams`] #[storage_alias] pub type StaticTierParams = - StorageValue, TierParameters<::NumberOfTiers>>; -} - -const PALLET_MIGRATIONS_ID: &[u8; 16] = b"dapp-staking-mbm"; - -pub struct LazyMigration(PhantomData<(T, W)>); - -impl SteppedMigration for LazyMigration { - type Cursor = ::AccountId; - // Without the explicit length here the construction of the ID would not be infallible. - type Identifier = MigrationId<16>; - - /// The identifier of this migration. Which should be globally unique. - fn id() -> Self::Identifier { - MigrationId { - pallet_id: *PALLET_MIGRATIONS_ID, - version_from: 0, - version_to: 1, - } - } - - fn step( - mut cursor: Option, - meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { - let required = W::step(); - // If there is not enough weight for a single step, return an error. This case can be - // problematic if it is the first migration that ran in this block. But there is nothing - // that we can do about it here. - if meter.remaining().any_lt(required) { - return Err(SteppedMigrationError::InsufficientWeight { required }); - } - - let mut count = 0u32; - let mut migrated = 0u32; - let current_block_number = - frame_system::Pallet::::block_number().saturated_into::(); - - // We loop here to do as much progress as possible per step. - loop { - if meter.try_consume(required).is_err() { - break; - } - - let mut iter = if let Some(last_key) = cursor { - // If a cursor is provided, start iterating from the stored value - // corresponding to the last key processed in the previous step. - // Note that this only works if the old and the new map use the same way to hash - // storage keys. - Ledger::::iter_from(Ledger::::hashed_key_for(last_key)) - } else { - // If no cursor is provided, start iterating from the beginning. - Ledger::::iter() - }; - - // If there's a next item in the iterator, perform the migration. - if let Some((ref last_key, mut ledger)) = iter.next() { - // inc count - count.saturating_inc(); - - if ledger.unlocking.is_empty() { - // no unlocking for this account, nothing to update - // Return the processed key as the new cursor. - cursor = Some(last_key.clone()); - continue; - } - for chunk in ledger.unlocking.iter_mut() { - if current_block_number >= chunk.unlock_block { - continue; // chunk already unlocked - } - let remaining_blocks = chunk.unlock_block.saturating_sub(current_block_number); - chunk.unlock_block.saturating_accrue(remaining_blocks); - } - - // Override ledger - Ledger::::insert(last_key, ledger); - - // inc migrated - migrated.saturating_inc(); - - // Return the processed key as the new cursor. - cursor = Some(last_key.clone()) - } else { - // Signal that the migration is complete (no more items to process). - cursor = None; - break; - } - } - log::info!(target: LOG_TARGET, "🚚 iterated {count} entries, migrated {migrated}"); - Ok(cursor) - } -} - -/// Double the remaining block for next era start -pub struct AdjustEraMigration(PhantomData); - -impl OnRuntimeUpgrade for AdjustEraMigration { - fn on_runtime_upgrade() -> Weight { - log::info!("🚚 migrated to async backing, adjust next era start"); - ActiveProtocolState::::mutate_exists(|maybe| { - if let Some(state) = maybe { - let current_block_number = - frame_system::Pallet::::block_number().saturated_into::(); - let remaining = state.next_era_start.saturating_sub(current_block_number); - state.next_era_start.saturating_accrue(remaining); - } - }); - T::DbWeight::get().reads_writes(1, 1) - } -} - -pub const EXPECTED_PALLET_DAPP_STAKING_VERSION: u16 = 9; - -pub struct DappStakingCleanupMigration(PhantomData); -impl OnRuntimeUpgrade for DappStakingCleanupMigration { - fn on_runtime_upgrade() -> Weight { - let dapp_staking_storage_version = - as GetStorageVersion>::on_chain_storage_version(); - if dapp_staking_storage_version != EXPECTED_PALLET_DAPP_STAKING_VERSION { - log::info!("Aborting migration due to unexpected on-chain storage versions for pallet-dapp-staking: {:?}. Expectation was: {:?}.", dapp_staking_storage_version, EXPECTED_PALLET_DAPP_STAKING_VERSION); - return T::DbWeight::get().reads(1); - } - - let pallet_prefix: &[u8] = b"DappStaking"; - let result = - clear_storage_prefix(pallet_prefix, b"ActiveBonusUpdateState", &[], None, None); - log::info!( - "cleanup dAppStaking migration result: {:?}", - result.deconstruct() - ); - - T::DbWeight::get().reads_writes(1, 1) - } + StorageValue, TierParameters<::NumberOfTiers>, OptionQuery>; } diff --git a/pallets/dapp-staking/src/test/migrations.rs b/pallets/dapp-staking/src/test/migrations.rs deleted file mode 100644 index d1e1109ac..000000000 --- a/pallets/dapp-staking/src/test/migrations.rs +++ /dev/null @@ -1,85 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Astar is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Astar. If not, see . - -#![cfg(all(test, not(feature = "runtime-benchmarks")))] - -use crate::test::mock::*; -use crate::{AccountLedger, CurrentEraInfo, EraInfo, Ledger, UnlockingChunk}; -use frame_support::traits::OnRuntimeUpgrade; - -#[test] -fn lazy_migrations() { - ExtBuilder::default().build_and_execute(|| { - Ledger::::set( - &1, - AccountLedger { - locked: 1000, - unlocking: vec![ - UnlockingChunk { - amount: 100, - unlock_block: 5, - }, - UnlockingChunk { - amount: 100, - unlock_block: 20, - }, - ] - .try_into() - .unwrap(), - staked: Default::default(), - staked_future: None, - contract_stake_count: 0, - }, - ); - CurrentEraInfo::::put(EraInfo { - total_locked: 1000, - unlocking: 200, - current_stake_amount: Default::default(), - next_stake_amount: Default::default(), - }); - - // go to block before migration - run_to_block(9); - - // onboard MBMs - AllPalletsWithSystem::on_runtime_upgrade(); - run_to_block(10); - - assert_eq!( - Ledger::::get(&1), - AccountLedger { - locked: 1000, - unlocking: vec![ - UnlockingChunk { - amount: 100, - unlock_block: 5, // already unlocked - }, - UnlockingChunk { - amount: 100, - unlock_block: 30, // double remaining blocks - }, - ] - .try_into() - .unwrap(), - staked: Default::default(), - staked_future: None, - contract_stake_count: 0, - } - ); - }) -} diff --git a/pallets/dapp-staking/src/test/mock.rs b/pallets/dapp-staking/src/test/mock.rs index 754b74635..f10428f2f 100644 --- a/pallets/dapp-staking/src/test/mock.rs +++ b/pallets/dapp-staking/src/test/mock.rs @@ -36,7 +36,7 @@ use sp_std::cell::RefCell; use astar_primitives::{ dapp_staking::{ - Observer as DappStakingObserver, SmartContract, StandardTierSlots, STANDARD_TIER_SLOTS_ARGS, + Observer as DappStakingObserver, SmartContract, StandardTierSlots, FIXED_TIER_SLOTS_ARGS, }, Balance, BlockNumber, }; @@ -88,8 +88,7 @@ parameter_types! { #[derive_impl(pallet_migrations::config_preludes::TestDefaultConfig)] impl pallet_migrations::Config for Test { #[cfg(not(feature = "runtime-benchmarks"))] - type Migrations = - (crate::migration::LazyMigration>,); + type Migrations = (); #[cfg(feature = "runtime-benchmarks")] type Migrations = pallet_migrations::mock_helpers::MockedMigrations; type MaxServiceWeight = MaxServiceWeight; @@ -225,6 +224,7 @@ impl pallet_dapp_staking::Config for Test { type EraRewardSpanLength = ConstU32<8>; type RewardRetentionInPeriods = ConstU32<2>; type MaxNumberOfContracts = ConstU32<10>; + type MaxNumberOfContractsLegacy = ConstU32<10>; type MaxUnlockingChunks = ConstU32<5>; type MinimumLockedAmount = ConstU128; type UnlockingPeriod = ConstU32<2>; @@ -341,7 +341,8 @@ impl ExtBuilder { }, ]) .unwrap(), - slot_number_args: STANDARD_TIER_SLOTS_ARGS, + slot_number_args: FIXED_TIER_SLOTS_ARGS, + tier_rank_multipliers: BoundedVec::try_from(vec![0, 24_000, 46_700, 0]).unwrap(), }; let total_issuance = ::Currency::total_issuance(); diff --git a/pallets/dapp-staking/src/test/mod.rs b/pallets/dapp-staking/src/test/mod.rs index 7c891a6b2..f5baecd0a 100644 --- a/pallets/dapp-staking/src/test/mod.rs +++ b/pallets/dapp-staking/src/test/mod.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -mod migrations; pub(crate) mod mock; mod testing_utils; mod tests; diff --git a/pallets/dapp-staking/src/test/tests.rs b/pallets/dapp-staking/src/test/tests.rs index c502e60da..a33808a96 100644 --- a/pallets/dapp-staking/src/test/tests.rs +++ b/pallets/dapp-staking/src/test/tests.rs @@ -42,7 +42,7 @@ use sp_runtime::{ use astar_primitives::{ dapp_staking::{ CycleConfiguration, EraNumber, RankedTier, SmartContractHandle, StakingRewardHandler, - TierSlots, + TierSlots, STANDARD_TIER_SLOTS_ARGS, }, Balance, BlockNumber, }; @@ -2685,6 +2685,13 @@ fn force_with_safeguard_on_fails() { #[test] fn tier_config_recalculation_works() { ExtBuilder::default().build_and_execute(|| { + // Setup for price based slot capacity + StaticTierParams::::mutate(|params| { + params.slot_number_args = STANDARD_TIER_SLOTS_ARGS; + }); + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + let init_price = NATIVE_PRICE.with(|v| v.borrow().clone()); let init_tier_config = TierConfig::::get(); @@ -2795,19 +2802,20 @@ fn get_dapp_tier_assignment_and_rewards_basic_example_works() { ExtBuilder::default().build_and_execute(|| { // Tier config is specially adapted for this test. TierConfig::::mutate(|config| { - config.slots_per_tier = BoundedVec::try_from(vec![2, 5, 13, 20]).unwrap(); + config.slots_per_tier = BoundedVec::try_from(vec![0, 6, 10, 0]).unwrap(); }); // Scenario: - // - 1st tier is filled up, with one dApp satisfying the threshold but not making it due to lack of tier capacity - // - 2nd tier has 2 dApps - 1 that could make it into the 1st tier and one that's supposed to be in the 2nd tier + // - 1st tier is empty, with 3 dApps satisfying the threshold but not making it due to lack of tier capacity + // - 2nd tier has 5 dApps - 3 that could make it into the 1st tier (expected to be rank 10) and 2 that are supposed to be in the 2nd tier // - 3rd tier has no dApps - // - 4th tier has 2 dApps + // - 4th tier has no dApps due to lack of tier capacity // - 1 dApp doesn't make it into any tier // Register smart contracts + let tier_params = StaticTierParams::::get(); let tier_config = TierConfig::::get(); - let number_of_smart_contracts = tier_config.slots_per_tier[0] + 1 + 1 + 0 + 2 + 1; + let number_of_smart_contracts = 0 + 5 + 0 + 2 + 1; let smart_contracts: Vec<_> = (1..=number_of_smart_contracts) .map(|x| { let smart_contract = MockSmartContract::Wasm(x.into()); @@ -2824,16 +2832,19 @@ fn get_dapp_tier_assignment_and_rewards_basic_example_works() { assert_stake(account, smart_contract, amount); } - // 1st tier is completely filled up, with 1 more dApp not making it inside - for x in 0..tier_config.slots_per_tier[0] as Balance { - lock_and_stake( - dapp_index, - &smart_contracts[dapp_index], - tier_config.tier_thresholds[0] + x + 1, - ); - dapp_index += 1; - } - // One that won't make it into the 1st tier. + // 2nd tier - 3 that won't make it into the 1st tier due to lack of tier capacity. + lock_and_stake( + dapp_index, + &smart_contracts[dapp_index], + tier_config.tier_thresholds[0] + 2, + ); + dapp_index += 1; + lock_and_stake( + dapp_index, + &smart_contracts[dapp_index], + tier_config.tier_thresholds[0] + 1, + ); + dapp_index += 1; lock_and_stake( dapp_index, &smart_contracts[dapp_index], @@ -2841,13 +2852,19 @@ fn get_dapp_tier_assignment_and_rewards_basic_example_works() { ); dapp_index += 1; - // 2nd tier - 1 dedicated dApp + // 2nd tier - 2 dedicated dApp lock_and_stake( dapp_index, &smart_contracts[dapp_index], tier_config.tier_thresholds[0] - 1, ); dapp_index += 1; + lock_and_stake( + dapp_index, + &smart_contracts[dapp_index], + tier_config.tier_thresholds[0] - 2, + ); + dapp_index += 1; // 3rd tier is empty // 4th tier has 2 dApps @@ -2869,18 +2886,16 @@ fn get_dapp_tier_assignment_and_rewards_basic_example_works() { // Finally, the actual test let protocol_state = ActiveProtocolState::::get(); - let dapp_reward_pool = 1000000; + let dapp_reward_pool: Balance = 1_000_000; let (tier_assignment, counter) = DappStaking::get_dapp_tier_assignment_and_rewards( protocol_state.era + 1, protocol_state.period_number(), dapp_reward_pool, ); - // There's enough reward to satisfy 100% reward per rank. - // Slot reward is 60_000 therefore expected rank reward is 6_000 assert_eq!( tier_assignment.rank_rewards, - BoundedVec::>::try_from(vec![0, 6_000, 0, 0]).unwrap() + BoundedVec::>::try_from(vec![0, 3583, 0, 0]).unwrap() ); // Basic checks @@ -2889,44 +2904,77 @@ fn get_dapp_tier_assignment_and_rewards_basic_example_works() { assert_eq!(tier_assignment.rewards.len(), number_of_tiers as usize); assert_eq!( tier_assignment.dapps.len(), - number_of_smart_contracts as usize - 1, - "One contract doesn't make it into any tier." + number_of_smart_contracts as usize - 3, + "Three contract doesn't make it into any tier." ); assert_eq!(counter, number_of_smart_contracts); - // 1st tier checks - let (dapp_1_tier, dapp_2_tier) = (tier_assignment.dapps[&0], tier_assignment.dapps[&1]); - assert_eq!(dapp_1_tier, RankedTier::new_saturated(0, 0)); - assert_eq!(dapp_2_tier, RankedTier::new_saturated(0, 0)); - // 2nd tier checks - let (dapp_3_tier, dapp_4_tier) = (tier_assignment.dapps[&2], tier_assignment.dapps[&3]); + let (dapp_1_tier, dapp_2_tier, dapp_3_tier, dapp_4_tier, dapp_5_tier) = ( + tier_assignment.dapps[&0], + tier_assignment.dapps[&1], + tier_assignment.dapps[&2], + tier_assignment.dapps[&3], + tier_assignment.dapps[&4], + ); + assert_eq!(dapp_1_tier, RankedTier::new_saturated(1, 10)); + assert_eq!(dapp_2_tier, RankedTier::new_saturated(1, 10)); assert_eq!(dapp_3_tier, RankedTier::new_saturated(1, 10)); assert_eq!(dapp_4_tier, RankedTier::new_saturated(1, 9)); - - // 4th tier checks - let (dapp_5_tier, dapp_6_tier) = (tier_assignment.dapps[&4], tier_assignment.dapps[&5]); - assert_eq!(dapp_5_tier, RankedTier::new_saturated(3, 0)); - assert_eq!(dapp_6_tier, RankedTier::new_saturated(3, 0)); - - // Sanity check - last dapp should not exists in the tier assignment - assert!(tier_assignment - .dapps - .get(&dapp_index.try_into().unwrap()) - .is_none()); - - // Check that rewards are calculated correctly - tier_config - .reward_portion - .iter() - .zip(tier_config.slots_per_tier.iter()) - .enumerate() - .for_each(|(idx, (reward_portion, slots))| { - let total_tier_allocation = *reward_portion * dapp_reward_pool; - let tier_reward: Balance = total_tier_allocation / (*slots as Balance); - - assert_eq!(tier_assignment.rewards[idx], tier_reward,); - }); + assert_eq!(dapp_5_tier, RankedTier::new_saturated(1, 9)); + + // === Verify reward calculations === + // Tier 0: 40% of 1M = 400,000 ASTR + // multiplier = 0% → compute_tier_rewards returns (0, 0) + assert_eq!(tier_assignment.rewards[0], 0); + assert_eq!(tier_assignment.rank_rewards[0], 0); + + // Tier 1: 30% of 1M = 300,000 ASTR + let tier_1_allocation = Permill::from_percent(30) * dapp_reward_pool; + let tier_1_slots = tier_config.slots_per_tier[1]; + let tier_1_filled = 5u32; + let tier_1_ranks_sum = 10u32 + 10 + 10 + 9 + 9; // 48 + let tier_1_multiplier = tier_params.tier_rank_multipliers[1]; + let (expected_reward_1, expected_rank_reward_1) = DappStaking::compute_tier_rewards( + tier_1_allocation, + tier_1_slots, + tier_1_filled, + tier_1_ranks_sum, + tier_1_multiplier, + ); + assert_eq!(tier_assignment.rewards[1], expected_reward_1); + assert_eq!(tier_assignment.rank_rewards[1], expected_rank_reward_1); + + // Tier 2: 20% of 1M = 200,000 ASTR + // empty tier (unminted rewards) + assert_eq!(tier_assignment.rewards[2], 0); + assert_eq!(tier_assignment.rank_rewards[2], 0); + + // Tier 3: 10% of 1M = 100,000 ASTR + let tier_3_allocation = Permill::from_percent(10) * dapp_reward_pool; + let tier_3_slots = tier_config.slots_per_tier[3]; + let tier_3_filled = 2u32; + let tier_3_ranks_sum = 0u32; + let tier_3_multiplier = tier_params.tier_rank_multipliers[3]; + let (expected_reward_3, expected_rank_reward_3) = DappStaking::compute_tier_rewards( + tier_3_allocation, + tier_3_slots, + tier_3_filled, + tier_3_ranks_sum, + tier_3_multiplier, + ); + assert_eq!(tier_assignment.rewards[3], expected_reward_3); + assert_eq!(tier_assignment.rank_rewards[3], expected_rank_reward_3); + + // === Verify claim formula produces capped total rewards === + let dapp_0_reward = tier_assignment.rewards[1] + 10 * tier_assignment.rank_rewards[1]; + let dapp_1_reward = tier_assignment.rewards[1] + 10 * tier_assignment.rank_rewards[1]; + let dapp_2_reward = tier_assignment.rewards[1] + 10 * tier_assignment.rank_rewards[1]; + let dapp_3_reward = tier_assignment.rewards[1] + 9 * tier_assignment.rank_rewards[1]; + let dapp_4_reward = tier_assignment.rewards[1] + 9 * tier_assignment.rank_rewards[1]; + let tier_1_total_disbursed = + dapp_0_reward + dapp_1_reward + dapp_2_reward + dapp_3_reward + dapp_4_reward; + assert!(tier_1_total_disbursed <= tier_1_allocation); }) } @@ -2961,6 +3009,10 @@ fn get_dapp_tier_assignment_and_rewards_zero_slots_per_tier_works() { tier_assignment.rewards[0].is_zero(), "1st tier has no slots so no rewards should be assigned to it." ); + assert!( + tier_assignment.rank_rewards[0].is_zero(), + "1st tier has no slots so no rank rewards should be assigned." + ); // Regardless of that, other tiers shouldn't benefit from this assert!(tier_assignment.rewards.iter().sum::() < dapp_reward_pool); @@ -3462,6 +3514,13 @@ fn safeguard_configurable_by_genesis_config() { #[test] fn base_number_of_slots_is_respected() { ExtBuilder::default().build_and_execute(|| { + // Setup for price based slot capacity + StaticTierParams::::mutate(|params| { + params.slot_number_args = STANDARD_TIER_SLOTS_ARGS; + }); + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + // 0. Get expected number of slots for the base price let total_issuance = ::Currency::total_issuance(); let base_native_price = ::BaseNativeCurrencyPrice::get(); @@ -3619,14 +3678,11 @@ fn ranking_will_calc_reward_correctly() { (4, RankedTier::new_saturated(3, 0)), ])) .unwrap(), - rewards: BoundedVec::try_from(vec![200_000, 100_000, 100_000, 5_000]).unwrap(), + rewards: BoundedVec::try_from(vec![200_000, 58_823, 28_019, 5_000]).unwrap(), period: 1, - // Tier 0 has no ranking therefore no rank reward. - // For tier 1 there's not enough reward to satisfy 100% reward per rank. - // Only one slot is empty. Slot reward is 100_000 therefore expected rank reward is 100_000 / 19 (ranks_sum). - // Tier 2 has ranking but there's no empty slot therefore no rank reward. - // Tier 3 has no ranking therefore no rank reward. - rank_rewards: BoundedVec::try_from(vec![0, 5_263, 0, 0]).unwrap() + // Tier 0 has no ranking multiplier therefore no rank reward. + // Tier 3 has no ranking multiplier therefore no rank reward. + rank_rewards: BoundedVec::try_from(vec![0, 8_235, 10_282, 0]).unwrap() } ); @@ -3638,44 +3694,49 @@ fn ranking_will_calc_reward_correctly() { #[test] fn claim_dapp_reward_with_rank() { ExtBuilder::default().build_and_execute(|| { - let total_issuance = ::Currency::total_issuance(); - - // Register smart contract, lock&stake some amount + // Register smart contract let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let alice = 2; - let amount = Perbill::from_parts(11_000_000) * total_issuance; // very close to tier 0 so will enter tier 1 with rank 9 + // Stake amount that puts dApp in Tier 1 with a specific rank + // Assuming tier_0=100, tier_1=50 + // Stake 75 → rank = (75-50)/5 = 5 + let amount = 75; assert_lock(alice, amount); assert_stake(alice, &smart_contract, amount); - // Advance 2 eras so we have an entry for reward claiming + // Advance 2 eras for reward claiming advance_to_era(ActiveProtocolState::::get().era + 2); let era = ActiveProtocolState::::get().era - 1; let tiers = DAppTiers::::get(era).unwrap(); - let slot_reward = tiers.rewards[1]; + // Verify tier assignment + let ranked_tier = tiers.dapps.get(&0).unwrap(); + assert_eq!(ranked_tier.tier(), 1, "Should be in tier 1"); + + let expected_rank = 5; // (75-50)/(50/10) = 25/5 = 5 + assert_eq!(ranked_tier.rank(), expected_rank); + + let base_reward = tiers.rewards[1]; let rank_reward = tiers.rank_rewards[1]; - // Claim dApp reward & verify event + // Calculate expected total reward + let expected_total_reward = base_reward + expected_rank as Balance * rank_reward; + + // Claim dApp reward assert_ok!(DappStaking::claim_dapp_reward( RuntimeOrigin::signed(alice), smart_contract.clone(), era, )); - let expected_rank = 9; - let expected_total_reward = slot_reward + expected_rank * rank_reward; - assert_eq!(slot_reward, 15_000_000); - assert_eq!(rank_reward, 1_500_000); // slot_reward / 10 - assert_eq!(expected_total_reward, 28_500_000); - System::assert_last_event(RuntimeEvent::DappStaking(Event::DAppReward { beneficiary: 1, smart_contract: smart_contract.clone(), tier_id: 1, - rank: 9, + rank: expected_rank, era, amount: expected_total_reward, })); diff --git a/pallets/dapp-staking/src/test/tests_types.rs b/pallets/dapp-staking/src/test/tests_types.rs index 3810dfaa9..edd1a86cc 100644 --- a/pallets/dapp-staking/src/test/tests_types.rs +++ b/pallets/dapp-staking/src/test/tests_types.rs @@ -17,7 +17,9 @@ // along with Astar. If not, see . use astar_primitives::{ - dapp_staking::{RankedTier, StandardTierSlots, STANDARD_TIER_SLOTS_ARGS}, + dapp_staking::{ + RankedTier, StandardTierSlots, FIXED_TIER_SLOTS_ARGS, STANDARD_TIER_SLOTS_ARGS, + }, Balance, }; use frame_support::{assert_ok, parameter_types}; @@ -3487,7 +3489,8 @@ fn tier_params_check_is_ok() { }, ]) .unwrap(), - slot_number_args: STANDARD_TIER_SLOTS_ARGS, + slot_number_args: FIXED_TIER_SLOTS_ARGS, + tier_rank_multipliers: BoundedVec::try_from(vec![10_000, 24_000, 46_700]).unwrap(), }; assert!(params.is_valid()); @@ -3572,6 +3575,26 @@ fn tier_params_check_is_ok() { ]) .unwrap(); assert!(!invalid_dynamic_params.is_valid()); + + // 7th scenario - tier_rank_multipliers length mismatch with number of tiers + let mut invalid_tier_points = params.clone(); + invalid_tier_points.tier_rank_multipliers = BoundedVec::try_from(vec![ + 10_000, 24_000, + // Missing 3rd tier multiplier + ]) + .unwrap(); + assert!(!invalid_tier_points.is_valid()); + + // 8th scenario - multiplier is capped + let mut valid_tier_points = params.clone(); + valid_tier_points.tier_rank_multipliers = + BoundedVec::try_from(vec![10_000, 100_000, 100_000]).unwrap(); + assert!(valid_tier_points.is_valid()); + + let mut invalid_tier_points = params.clone(); + invalid_tier_points.tier_rank_multipliers = + BoundedVec::try_from(vec![10_000, 100_001, 100_000]).unwrap(); + assert!(!invalid_tier_points.is_valid()); } #[test] @@ -3614,7 +3637,8 @@ fn tier_configuration_basic_tests() { }, ]) .unwrap(), - slot_number_args: STANDARD_TIER_SLOTS_ARGS, + slot_number_args: FIXED_TIER_SLOTS_ARGS, + tier_rank_multipliers: BoundedVec::try_from(vec![0, 24_000, 46_700, 0]).unwrap(), }; assert!(params.is_valid(), "Example params must be valid!"); @@ -3890,6 +3914,7 @@ fn tier_configuration_calculate_new_with_maximum_threshold() { tier_thresholds: tier_thresholds_legacy, reward_portion: reward_portion.clone(), slot_number_args: STANDARD_TIER_SLOTS_ARGS, + tier_rank_multipliers: BoundedVec::try_from(vec![0, 24_000, 46_700, 0]).unwrap(), }; let params_with_max = TierParameters:: { @@ -3897,6 +3922,7 @@ fn tier_configuration_calculate_new_with_maximum_threshold() { tier_thresholds: tier_thresholds_with_max, reward_portion: reward_portion.clone(), slot_number_args: STANDARD_TIER_SLOTS_ARGS, + tier_rank_multipliers: BoundedVec::try_from(vec![0, 24_000, 46_700, 0]).unwrap(), }; // Create a starting configuration with some values diff --git a/pallets/dapp-staking/src/types.rs b/pallets/dapp-staking/src/types.rs index aa44e0508..bbceaee1a 100644 --- a/pallets/dapp-staking/src/types.rs +++ b/pallets/dapp-staking/src/types.rs @@ -87,7 +87,7 @@ pub type AccountLedgerFor = AccountLedger<::MaxUnlockingChunks>; // Convenience type for `DAppTierRewards` usage. pub type DAppTierRewardsFor = - DAppTierRewards<::MaxNumberOfContracts, ::NumberOfTiers>; + DAppTierRewards<::MaxNumberOfContractsLegacy, ::NumberOfTiers>; // Convenience type for `EraRewardSpan` usage. pub type EraRewardSpanFor = EraRewardSpan<::EraRewardSpanLength>; @@ -879,7 +879,7 @@ impl Iterator for EraStakePairIter { if self.start_era <= self.end_era { let value = (self.start_era, self.amount); self.start_era.saturating_inc(); - return Some(value); + Some(value) } else { None } @@ -1710,6 +1710,15 @@ pub struct TierParameters> { /// This can be made more generic in the future in case more complex equations are required. /// But for now this simple tuple serves the purpose. pub(crate) slot_number_args: (u64, u64), + /// Rank multiplier per tier in bips (100% = 10_000 bips): + /// defines how much rank 10 earns relative to rank 0. + /// + /// Example: + /// - `24_000` → rank 10 earns 2.4× rank 0 + /// ⇒ per-rank increment = (240% − 100%) / 10 = +14% per rank + /// - `10_000` → rank rewards disabled (rank 0..10 all earn the same) + /// - `0` → no rewards for all slots + pub(crate) tier_rank_multipliers: BoundedVec, } impl> TierParameters { @@ -1756,10 +1765,22 @@ impl> TierParameters { } } + // - Tier 0: must be <= 100% (no rank bonus for the top tier) + match self.tier_rank_multipliers.first() { + Some(m0) if *m0 <= 10_000 => {} + _ => return false, + } + + // Safety cap + if self.tier_rank_multipliers.iter().any(|m| *m > 100_000) { + return false; + } + let number_of_tiers: usize = NT::get() as usize; number_of_tiers == self.reward_portion.len() && number_of_tiers == self.slot_distribution.len() && number_of_tiers == self.tier_thresholds.len() + && number_of_tiers == self.tier_rank_multipliers.len() } } @@ -1807,6 +1828,21 @@ impl, T: TierSlotsFunc, P: Get> TiersConfiguration &BoundedVec { + &self.tier_thresholds + } + + /// Returns the number of available slots for each tier. + pub fn slots_per_tier(&self) -> &BoundedVec { + &self.slots_per_tier + } + + /// Returns the reward distribution portion assigned to each tier. + pub fn reward_portion(&self) -> &BoundedVec { + &self.reward_portion + } + /// Calculate new `TiersConfiguration`, based on the old settings, current native currency price and tier configuration. pub fn calculate_new( &self, diff --git a/pallets/dapp-staking/src/weights.rs b/pallets/dapp-staking/src/weights.rs index e5c0bea48..357ec2b1a 100644 --- a/pallets/dapp-staking/src/weights.rs +++ b/pallets/dapp-staking/src/weights.rs @@ -74,7 +74,6 @@ pub trait WeightInfo { fn on_initialize_build_and_earn_to_build_and_earn() -> Weight; fn dapp_tier_assignment(x: u32, ) -> Weight; fn on_idle_cleanup() -> Weight; - fn step() -> Weight; fn set_static_tier_params() -> Weight; } @@ -543,17 +542,6 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: `DappStaking::Ledger` (r:2 w:1) - /// Proof: `DappStaking::Ledger` (`max_values`: None, `max_size`: Some(310), added: 2785, mode: `MaxEncodedLen`) - fn step() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `6560` - // Minimum execution time: 13_041_000 picoseconds. - Weight::from_parts(13_375_000, 6560) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } /// Storage: `DappStaking::StaticTierParams` (r:0 w:1) /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(87), added: 582, mode: `MaxEncodedLen`) fn set_static_tier_params() -> Weight { @@ -1030,17 +1018,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: `DappStaking::Ledger` (r:2 w:1) - /// Proof: `DappStaking::Ledger` (`max_values`: None, `max_size`: Some(310), added: 2785, mode: `MaxEncodedLen`) - fn step() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `6560` - // Minimum execution time: 13_041_000 picoseconds. - Weight::from_parts(13_375_000, 6560) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } /// Storage: `DappStaking::StaticTierParams` (r:0 w:1) /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(87), added: 582, mode: `MaxEncodedLen`) fn set_static_tier_params() -> Weight { diff --git a/precompiles/dapp-staking/src/test/mock.rs b/precompiles/dapp-staking/src/test/mock.rs index d577eebe5..2a9af5446 100644 --- a/precompiles/dapp-staking/src/test/mock.rs +++ b/precompiles/dapp-staking/src/test/mock.rs @@ -242,6 +242,7 @@ impl pallet_dapp_staking::Config for Test { type EraRewardSpanLength = ConstU32<8>; type RewardRetentionInPeriods = ConstU32<2>; type MaxNumberOfContracts = ConstU32<10>; + type MaxNumberOfContractsLegacy = ConstU32<10>; type MaxUnlockingChunks = ConstU32<5>; type MinimumLockedAmount = ConstU128<10>; type UnlockingPeriod = ConstU32<2>; @@ -303,6 +304,7 @@ impl ExternalityBuilder { slot_number_args: STANDARD_TIER_SLOTS_ARGS, slots_per_tier: vec![10, 20, 30, 40], safeguard: None, + tier_rank_multipliers: vec![10_000u32, 20_000, 20_000, 20_000], _config: PhantomData, }, &mut storage, diff --git a/primitives/src/dapp_staking.rs b/primitives/src/dapp_staking.rs index edf322127..4fdd997e6 100644 --- a/primitives/src/dapp_staking.rs +++ b/primitives/src/dapp_staking.rs @@ -37,7 +37,7 @@ pub type PeriodNumber = u32; pub type DAppId = u16; /// Tier Id type pub type TierId = u8; -// Tier Rank type +/// Tier Rank type pub type Rank = u8; /// Configuration for cycles, periods, subperiods & eras. @@ -208,6 +208,8 @@ impl TierSlots for StandardTierSlots { /// Standard tier slots arguments. /// Initially decided for Astar, during the Tokenomics 2.0 work. pub const STANDARD_TIER_SLOTS_ARGS: (u64, u64) = (1000, 50); +/// Decided for Astar, during the Tokenomics 3.0 revamp. +pub const FIXED_TIER_SLOTS_ARGS: (u64, u64) = (0, 16); /// RankedTier is wrapper around u8 to hold both tier and rank. u8 has 2 bytes (8bits) and they're using in this order `0xrank_tier`. /// First 4 bits are used to hold rank and second 4 bits are used to hold tier. diff --git a/runtime/astar/src/genesis_config.rs b/runtime/astar/src/genesis_config.rs index dbeaaf5f5..1476e1375 100644 --- a/runtime/astar/src/genesis_config.rs +++ b/runtime/astar/src/genesis_config.rs @@ -17,7 +17,10 @@ // along with Astar. If not, see . use crate::*; -use astar_primitives::{evm::EVM_REVERT_CODE, genesis::GenesisAccount, parachain::ASTAR_ID}; +use astar_primitives::{ + dapp_staking::FIXED_TIER_SLOTS_ARGS, evm::EVM_REVERT_CODE, genesis::GenesisAccount, + parachain::ASTAR_ID, +}; /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { @@ -58,6 +61,9 @@ pub fn default_config(para_id: u32) -> serde_json::Value { .map(|x| (x.clone(), 1_000_000_000 * ASTR)) .collect::>(); + let slots_per_tier = vec![0, 6, 10, 0]; + let tier_rank_multipliers: Vec = vec![0, 24_000, 46_700, 0]; + let config = RuntimeGenesisConfig { system: Default::default(), #[cfg(feature = "astar-sudo")] @@ -125,40 +131,37 @@ pub fn default_config(para_id: u32) -> serde_json::Value { transaction_payment: Default::default(), dapp_staking: DappStakingConfig { reward_portion: vec![ - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_percent(70), Permill::from_percent(30), - Permill::from_percent(20), - Permill::from_percent(10), + Permill::from_percent(0), ], slot_distribution: vec![ - Permill::from_percent(10), - Permill::from_percent(20), - Permill::from_percent(30), - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_parts(375_000), // 37.5% + Permill::from_parts(625_000), // 62.5% + Permill::from_percent(0), ], - // percentages below are calculated based on a total issuance at the time when dApp staking v3 was launched (84.3M) + // percentages below are calculated based on a total issuance at the time when dApp staking v3 was revamped (8.6B) tier_thresholds: vec![ - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(35_700_000), // 3.57% - minimum_required_percentage: Perbill::from_parts(23_800_000), // 2.38% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(23_200_000), // 2.32% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(8_900_000), // 0.89% - minimum_required_percentage: Perbill::from_parts(6_000_000), // 0.6% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(9_300_000), // 0.93% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(2_380_000), // 0.238% - minimum_required_percentage: Perbill::from_parts(1_790_000), // 0.179% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(3_500_000), // 0.35% }, + // Tier 3: unreachable dummy TierThreshold::FixedPercentage { - required_percentage: Perbill::from_parts(600_000), // 0.06% + required_percentage: Perbill::from_parts(0), // 0% }, ], - slots_per_tier: vec![10, 20, 30, 40], + slots_per_tier, + slot_number_args: FIXED_TIER_SLOTS_ARGS, safeguard: Some(false), + tier_rank_multipliers, ..Default::default() }, inflation: Default::default(), diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index a84daa281..f67dbc140 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -472,7 +472,8 @@ impl pallet_dapp_staking::Config for Runtime { type BaseNativeCurrencyPrice = BaseNativeCurrencyPrice; type EraRewardSpanLength = ConstU32<16>; type RewardRetentionInPeriods = ConstU32<4>; - type MaxNumberOfContracts = ConstU32<500>; + type MaxNumberOfContracts = ConstU32<16>; + type MaxNumberOfContractsLegacy = ConstU32<500>; type MaxUnlockingChunks = ConstU32<8>; type MinimumLockedAmount = MinimumStakingAmount; type UnlockingPeriod = ConstU32<9>; @@ -500,15 +501,15 @@ impl pallet_inflation::PayoutPerBlock> for Inflation pub struct InflationCycleConfig; impl CycleConfiguration for InflationCycleConfig { fn periods_per_cycle() -> u32 { - 3 + 1 } fn eras_per_voting_subperiod() -> u32 { - 11 + 1 } fn eras_per_build_and_earn_subperiod() -> u32 { - 111 + 364 } fn blocks_per_era() -> BlockNumber { @@ -1750,7 +1751,14 @@ pub type Executive = frame_executive::Executive< pub type Migrations = (Unreleased, Permanent); /// Unreleased migrations. Add new ones here: -pub type Unreleased = (); +pub type Unreleased = ( + pallet_dapp_staking::migration::versioned_migrations::V10ToV11< + Runtime, + pallet_dapp_staking::migration::DefaultTierParamsV11, + ConstU32<11>, + ConstU32<111>, + >, +); /// Migrations/checks that do not need to be versioned and can run on every upgrade. pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); diff --git a/runtime/astar/src/weights/pallet_dapp_staking.rs b/runtime/astar/src/weights/pallet_dapp_staking.rs index 41f2e1112..027007d39 100644 --- a/runtime/astar/src/weights/pallet_dapp_staking.rs +++ b/runtime/astar/src/weights/pallet_dapp_staking.rs @@ -20,7 +20,7 @@ //! Autogenerated weights for `pallet_dapp_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2025-12-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-02-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `gh-runner-01-ovh`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 @@ -56,8 +56,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_614_000 picoseconds. - Weight::from_parts(12_478_000, 0) + // Minimum execution time: 8_359_000 picoseconds. + Weight::from_parts(8_596_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `DappStaking::IntegratedDApps` (r:1 w:1) @@ -70,8 +70,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `3086` - // Minimum execution time: 18_271_000 picoseconds. - Weight::from_parts(18_397_000, 0) + // Minimum execution time: 16_561_000 picoseconds. + Weight::from_parts(16_759_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -82,8 +82,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 15_621_000 picoseconds. - Weight::from_parts(15_946_000, 0) + // Minimum execution time: 14_619_000 picoseconds. + Weight::from_parts(15_003_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -94,8 +94,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 15_482_000 picoseconds. - Weight::from_parts(15_984_000, 0) + // Minimum execution time: 14_892_000 picoseconds. + Weight::from_parts(15_238_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -110,8 +110,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 21_534_000 picoseconds. - Weight::from_parts(21_995_000, 0) + // Minimum execution time: 20_368_000 picoseconds. + Weight::from_parts(20_675_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) @@ -130,8 +130,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `138` // Estimated: `4764` - // Minimum execution time: 39_515_000 picoseconds. - Weight::from_parts(40_403_000, 0) + // Minimum execution time: 38_020_000 picoseconds. + Weight::from_parts(38_509_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -148,8 +148,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `158` // Estimated: `4764` - // Minimum execution time: 40_215_000 picoseconds. - Weight::from_parts(40_662_000, 0) + // Minimum execution time: 38_720_000 picoseconds. + Weight::from_parts(39_343_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -166,8 +166,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `158` // Estimated: `4764` - // Minimum execution time: 36_950_000 picoseconds. - Weight::from_parts(37_450_000, 0) + // Minimum execution time: 35_583_000 picoseconds. + Weight::from_parts(35_990_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -185,11 +185,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `190` // Estimated: `4764` - // Minimum execution time: 39_873_000 picoseconds. - Weight::from_parts(41_909_657, 0) + // Minimum execution time: 38_158_000 picoseconds. + Weight::from_parts(40_394_137, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 31_452 - .saturating_add(Weight::from_parts(103_521, 0).saturating_mul(x.into())) + // Standard Error: 5_223 + .saturating_add(Weight::from_parts(92_010, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -205,8 +205,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `200` // Estimated: `4764` - // Minimum execution time: 36_949_000 picoseconds. - Weight::from_parts(37_559_000, 0) + // Minimum execution time: 35_034_000 picoseconds. + Weight::from_parts(35_760_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -229,8 +229,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `274` // Estimated: `4764` - // Minimum execution time: 53_841_000 picoseconds. - Weight::from_parts(54_433_000, 0) + // Minimum execution time: 52_917_000 picoseconds. + Weight::from_parts(53_368_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -253,8 +253,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `459` // Estimated: `4764` - // Minimum execution time: 58_602_000 picoseconds. - Weight::from_parts(59_354_000, 0) + // Minimum execution time: 57_250_000 picoseconds. + Weight::from_parts(59_104_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -274,11 +274,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `541` // Estimated: `4764` - // Minimum execution time: 58_417_000 picoseconds. - Weight::from_parts(58_205_826, 0) + // Minimum execution time: 56_364_000 picoseconds. + Weight::from_parts(55_482_121, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 15_677 - .saturating_add(Weight::from_parts(1_917_365, 0).saturating_mul(x.into())) + // Standard Error: 3_957 + .saturating_add(Weight::from_parts(1_941_267, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -295,11 +295,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `519` // Estimated: `4764` - // Minimum execution time: 55_597_000 picoseconds. - Weight::from_parts(54_766_735, 0) + // Minimum execution time: 53_777_000 picoseconds. + Weight::from_parts(52_564_857, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 4_643 - .saturating_add(Weight::from_parts(1_964_351, 0).saturating_mul(x.into())) + // Standard Error: 7_719 + .saturating_add(Weight::from_parts(1_981_413, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -313,8 +313,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `275` // Estimated: `3775` - // Minimum execution time: 46_511_000 picoseconds. - Weight::from_parts(46_842_000, 0) + // Minimum execution time: 44_969_000 picoseconds. + Weight::from_parts(45_458_000, 0) .saturating_add(Weight::from_parts(0, 3775)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,10 +325,10 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn claim_dapp_reward() -> Weight { // Proof Size summary in bytes: - // Measured: `2672` + // Measured: `647` // Estimated: `5113` - // Minimum execution time: 70_754_000 picoseconds. - Weight::from_parts(72_647_000, 0) + // Minimum execution time: 29_381_000 picoseconds. + Weight::from_parts(30_312_000, 0) .saturating_add(Weight::from_parts(0, 5113)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -349,8 +349,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `322` // Estimated: `4764` - // Minimum execution time: 49_611_000 picoseconds. - Weight::from_parts(50_156_000, 0) + // Minimum execution time: 48_616_000 picoseconds. + Weight::from_parts(49_290_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -368,11 +368,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `257 + x * (73 ±0)` // Estimated: `4764 + x * (2653 ±0)` - // Minimum execution time: 48_067_000 picoseconds. - Weight::from_parts(44_647_745, 0) + // Minimum execution time: 46_102_000 picoseconds. + Weight::from_parts(43_349_342, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 12_548 - .saturating_add(Weight::from_parts(6_228_123, 0).saturating_mul(x.into())) + // Standard Error: 11_849 + .saturating_add(Weight::from_parts(6_251_196, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -385,8 +385,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `1486` - // Minimum execution time: 13_005_000 picoseconds. - Weight::from_parts(13_524_000, 0) + // Minimum execution time: 11_945_000 picoseconds. + Weight::from_parts(12_232_000, 0) .saturating_add(Weight::from_parts(0, 1486)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -408,8 +408,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `553` // Estimated: `6296` - // Minimum execution time: 92_804_000 picoseconds. - Weight::from_parts(93_355_000, 0) + // Minimum execution time: 91_271_000 picoseconds. + Weight::from_parts(91_921_000, 0) .saturating_add(Weight::from_parts(0, 6296)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(7)) @@ -432,8 +432,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `419` // Estimated: `6296` - // Minimum execution time: 83_894_000 picoseconds. - Weight::from_parts(85_431_000, 0) + // Minimum execution time: 82_417_000 picoseconds. + Weight::from_parts(83_201_000, 0) .saturating_add(Weight::from_parts(0, 6296)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(6)) @@ -443,17 +443,17 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) /// Proof: `DappStaking::TierConfig` (`max_values`: Some(1), `max_size`: Some(91), added: 586, mode: `MaxEncodedLen`) fn on_initialize_voting_to_build_and_earn() -> Weight { // Proof Size summary in bytes: - // Measured: `224` + // Measured: `217` // Estimated: `4254` - // Minimum execution time: 28_879_000 picoseconds. - Weight::from_parts(29_876_000, 0) + // Minimum execution time: 26_079_000 picoseconds. + Weight::from_parts(26_756_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -467,7 +467,7 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) @@ -476,10 +476,10 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn on_initialize_build_and_earn_to_voting() -> Weight { // Proof Size summary in bytes: - // Measured: `731` + // Measured: `783` // Estimated: `4254` - // Minimum execution time: 51_988_000 picoseconds. - Weight::from_parts(53_076_000, 0) + // Minimum execution time: 49_334_000 picoseconds. + Weight::from_parts(50_631_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(7)) @@ -489,7 +489,7 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) @@ -498,29 +498,31 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn on_initialize_build_and_earn_to_build_and_earn() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `269` // Estimated: `4254` - // Minimum execution time: 31_974_000 picoseconds. - Weight::from_parts(32_938_000, 0) + // Minimum execution time: 30_195_000 picoseconds. + Weight::from_parts(30_873_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: `DappStaking::ContractStake` (r:101 w:0) + /// Storage: `DappStaking::ContractStake` (r:17 w:0) /// Proof: `DappStaking::ContractStake` (`max_values`: Some(65535), `max_size`: Some(91), added: 2071, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:0) /// Proof: `DappStaking::TierConfig` (`max_values`: Some(1), `max_size`: Some(91), added: 586, mode: `MaxEncodedLen`) - /// The range of component `x` is `[0, 100]`. + /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) + /// The range of component `x` is `[0, 16]`. fn dapp_tier_assignment(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `97 + x * (32 ±0)` + // Measured: `210 + x * (33 ±0)` // Estimated: `3061 + x * (2071 ±0)` - // Minimum execution time: 8_154_000 picoseconds. - Weight::from_parts(10_541_455, 0) + // Minimum execution time: 10_336_000 picoseconds. + Weight::from_parts(14_531_778, 0) .saturating_add(Weight::from_parts(0, 3061)) - // Standard Error: 3_518 - .saturating_add(Weight::from_parts(2_947_593, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Standard Error: 16_087 + .saturating_add(Weight::from_parts(3_114_631, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(Weight::from_parts(0, 2071).saturating_mul(x.into())) } @@ -534,32 +536,20 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `293` // Estimated: `4254` - // Minimum execution time: 11_073_000 picoseconds. - Weight::from_parts(11_380_000, 0) + // Minimum execution time: 9_785_000 picoseconds. + Weight::from_parts(10_057_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `DappStaking::Ledger` (r:2 w:1) - /// Proof: `DappStaking::Ledger` (`max_values`: None, `max_size`: Some(310), added: 2785, mode: `MaxEncodedLen`) - fn step() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `6560` - // Minimum execution time: 20_821_000 picoseconds. - Weight::from_parts(21_079_000, 0) - .saturating_add(Weight::from_parts(0, 6560)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } /// Storage: `DappStaking::StaticTierParams` (r:0 w:1) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) fn set_static_tier_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_181_000 picoseconds. - Weight::from_parts(11_568_000, 0) + // Minimum execution time: 10_291_000 picoseconds. + Weight::from_parts(10_454_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index aae135fce..59cd1f29f 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -487,6 +487,7 @@ impl pallet_dapp_staking::Config for Runtime { type EraRewardSpanLength = ConstU32<8>; type RewardRetentionInPeriods = ConstU32<2>; type MaxNumberOfContracts = ConstU32<100>; + type MaxNumberOfContractsLegacy = ConstU32<100>; type MaxUnlockingChunks = ConstU32<5>; type MinimumLockedAmount = ConstU128; type UnlockingPeriod = ConstU32<2>; diff --git a/runtime/shibuya/src/genesis_config.rs b/runtime/shibuya/src/genesis_config.rs index c81e36964..5c35c868c 100644 --- a/runtime/shibuya/src/genesis_config.rs +++ b/runtime/shibuya/src/genesis_config.rs @@ -17,7 +17,10 @@ // along with Astar. If not, see . use crate::*; -use astar_primitives::{evm::EVM_REVERT_CODE, genesis::GenesisAccount, parachain::SHIBUYA_ID}; +use astar_primitives::{ + dapp_staking::FIXED_TIER_SLOTS_ARGS, evm::EVM_REVERT_CODE, genesis::GenesisAccount, + parachain::SHIBUYA_ID, +}; /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { @@ -58,6 +61,9 @@ pub fn default_config(para_id: u32) -> serde_json::Value { .map(|x| (x.clone(), 1_000_000_000 * SBY)) .collect::>(); + let slots_per_tier = vec![0, 6, 10, 0]; + let tier_rank_multipliers: Vec = vec![0, 24_000, 46_700, 0]; + let config = RuntimeGenesisConfig { system: Default::default(), sudo: SudoConfig { @@ -128,40 +134,37 @@ pub fn default_config(para_id: u32) -> serde_json::Value { transaction_payment: Default::default(), dapp_staking: DappStakingConfig { reward_portion: vec![ - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_percent(70), Permill::from_percent(30), - Permill::from_percent(20), - Permill::from_percent(10), + Permill::from_percent(0), ], slot_distribution: vec![ - Permill::from_percent(10), - Permill::from_percent(20), - Permill::from_percent(30), - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_parts(375_000), // 37.5% + Permill::from_parts(625_000), // 62.5% + Permill::from_percent(0), ], - // percentages below are calculated based on a total issuance at the time when dApp staking v3 was launched (147M) + // percentages below are calculated based on a total issuance at the time when dApp staking v3 was revamped (8.6B) tier_thresholds: vec![ - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(20_000), // 0.0020% - minimum_required_percentage: Perbill::from_parts(17_000), // 0.0017% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(23_200_000), // 2.32% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(13_000), // 0.0013% - minimum_required_percentage: Perbill::from_parts(10_000), // 0.0010% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(9_300_000), // 0.93% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(5_400), // 0.00054% - minimum_required_percentage: Perbill::from_parts(3_400), // 0.00034% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(3_500_000), // 0.35% }, + // Tier 3: unreachable dummy TierThreshold::FixedPercentage { - required_percentage: Perbill::from_parts(1_400), // 0.00014% + required_percentage: Perbill::from_parts(0), // 0% }, ], - slots_per_tier: vec![10, 20, 30, 40], + slots_per_tier, + slot_number_args: FIXED_TIER_SLOTS_ARGS, safeguard: Some(false), + tier_rank_multipliers, ..Default::default() }, inflation: Default::default(), diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index 57503bef2..362dd91a9 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -497,7 +497,8 @@ impl pallet_dapp_staking::Config for Runtime { type BaseNativeCurrencyPrice = BaseNativeCurrencyPrice; type EraRewardSpanLength = ConstU32<16>; type RewardRetentionInPeriods = ConstU32<2>; - type MaxNumberOfContracts = ConstU32<500>; + type MaxNumberOfContracts = ConstU32<16>; + type MaxNumberOfContractsLegacy = ConstU32<500>; type MaxUnlockingChunks = ConstU32<8>; type MinimumLockedAmount = MinimumStakingAmount; type UnlockingPeriod = ConstU32<4>; @@ -525,15 +526,15 @@ impl pallet_inflation::PayoutPerBlock> for Inflation pub struct InflationCycleConfig; impl CycleConfiguration for InflationCycleConfig { fn periods_per_cycle() -> PeriodNumber { - 2 + 1 } fn eras_per_voting_subperiod() -> EraNumber { - 8 + 1 } fn eras_per_build_and_earn_subperiod() -> EraNumber { - 20 + 27 } fn blocks_per_era() -> BlockNumber { @@ -1756,7 +1757,14 @@ pub type Executive = frame_executive::Executive< pub type Migrations = (Unreleased, Permanent); /// Unreleased migrations. Add new ones here: -pub type Unreleased = (); +pub type Unreleased = ( + pallet_dapp_staking::migration::versioned_migrations::V10ToV11< + Runtime, + pallet_dapp_staking::migration::DefaultTierParamsV11, + ConstU32<8>, + ConstU32<20>, + >, +); /// Migrations/checks that do not need to be versioned and can run on every upgrade. pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); diff --git a/runtime/shibuya/src/weights/pallet_dapp_staking.rs b/runtime/shibuya/src/weights/pallet_dapp_staking.rs index 7eece112a..9fcb2939c 100644 --- a/runtime/shibuya/src/weights/pallet_dapp_staking.rs +++ b/runtime/shibuya/src/weights/pallet_dapp_staking.rs @@ -20,7 +20,7 @@ //! Autogenerated weights for `pallet_dapp_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2025-12-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-02-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `gh-runner-01-ovh`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 @@ -56,8 +56,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_532_000 picoseconds. - Weight::from_parts(9_897_000, 0) + // Minimum execution time: 8_218_000 picoseconds. + Weight::from_parts(8_408_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `DappStaking::IntegratedDApps` (r:1 w:1) @@ -70,8 +70,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `3086` - // Minimum execution time: 18_129_000 picoseconds. - Weight::from_parts(18_554_000, 0) + // Minimum execution time: 16_500_000 picoseconds. + Weight::from_parts(16_772_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -82,8 +82,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 15_530_000 picoseconds. - Weight::from_parts(15_992_000, 0) + // Minimum execution time: 14_766_000 picoseconds. + Weight::from_parts(14_967_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -94,8 +94,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 15_450_000 picoseconds. - Weight::from_parts(15_816_000, 0) + // Minimum execution time: 14_077_000 picoseconds. + Weight::from_parts(14_713_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -110,8 +110,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 21_399_000 picoseconds. - Weight::from_parts(21_894_000, 0) + // Minimum execution time: 20_011_000 picoseconds. + Weight::from_parts(20_465_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) @@ -130,8 +130,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `138` // Estimated: `4764` - // Minimum execution time: 39_604_000 picoseconds. - Weight::from_parts(40_324_000, 0) + // Minimum execution time: 38_183_000 picoseconds. + Weight::from_parts(38_650_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -148,8 +148,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `156` // Estimated: `4764` - // Minimum execution time: 39_781_000 picoseconds. - Weight::from_parts(40_572_000, 0) + // Minimum execution time: 39_542_000 picoseconds. + Weight::from_parts(40_046_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -166,8 +166,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `156` // Estimated: `4764` - // Minimum execution time: 36_894_000 picoseconds. - Weight::from_parts(37_502_000, 0) + // Minimum execution time: 35_821_000 picoseconds. + Weight::from_parts(36_591_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -185,11 +185,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `187` // Estimated: `4764` - // Minimum execution time: 39_921_000 picoseconds. - Weight::from_parts(41_384_208, 0) + // Minimum execution time: 38_015_000 picoseconds. + Weight::from_parts(40_043_292, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 7_176 - .saturating_add(Weight::from_parts(139_066, 0).saturating_mul(x.into())) + // Standard Error: 9_733 + .saturating_add(Weight::from_parts(252_507, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -205,8 +205,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `182` // Estimated: `4764` - // Minimum execution time: 37_096_000 picoseconds. - Weight::from_parts(38_049_000, 0) + // Minimum execution time: 35_172_000 picoseconds. + Weight::from_parts(35_870_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -229,8 +229,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `272` // Estimated: `4764` - // Minimum execution time: 54_713_000 picoseconds. - Weight::from_parts(55_369_000, 0) + // Minimum execution time: 53_682_000 picoseconds. + Weight::from_parts(54_348_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -253,8 +253,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `453` // Estimated: `4764` - // Minimum execution time: 58_830_000 picoseconds. - Weight::from_parts(59_364_000, 0) + // Minimum execution time: 58_426_000 picoseconds. + Weight::from_parts(59_415_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -274,11 +274,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `522` // Estimated: `4764` - // Minimum execution time: 58_201_000 picoseconds. - Weight::from_parts(57_727_458, 0) + // Minimum execution time: 56_624_000 picoseconds. + Weight::from_parts(55_677_682, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 5_462 - .saturating_add(Weight::from_parts(1_989_282, 0).saturating_mul(x.into())) + // Standard Error: 5_007 + .saturating_add(Weight::from_parts(1_950_486, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -295,11 +295,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `501` // Estimated: `4764` - // Minimum execution time: 55_294_000 picoseconds. - Weight::from_parts(54_610_504, 0) + // Minimum execution time: 52_994_000 picoseconds. + Weight::from_parts(52_316_116, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 4_838 - .saturating_add(Weight::from_parts(1_998_977, 0).saturating_mul(x.into())) + // Standard Error: 6_182 + .saturating_add(Weight::from_parts(1_999_131, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -313,8 +313,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `271` // Estimated: `3775` - // Minimum execution time: 46_757_000 picoseconds. - Weight::from_parts(47_194_000, 0) + // Minimum execution time: 45_018_000 picoseconds. + Weight::from_parts(45_523_000, 0) .saturating_add(Weight::from_parts(0, 3775)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,10 +325,10 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn claim_dapp_reward() -> Weight { // Proof Size summary in bytes: - // Measured: `2672` + // Measured: `647` // Estimated: `5113` - // Minimum execution time: 71_261_000 picoseconds. - Weight::from_parts(72_272_000, 0) + // Minimum execution time: 29_934_000 picoseconds. + Weight::from_parts(30_849_000, 0) .saturating_add(Weight::from_parts(0, 5113)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -349,8 +349,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `317` // Estimated: `4764` - // Minimum execution time: 49_203_000 picoseconds. - Weight::from_parts(49_852_000, 0) + // Minimum execution time: 48_491_000 picoseconds. + Weight::from_parts(49_049_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -368,11 +368,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `255 + x * (73 ±0)` // Estimated: `4764 + x * (2653 ±0)` - // Minimum execution time: 47_596_000 picoseconds. - Weight::from_parts(43_009_084, 0) + // Minimum execution time: 45_909_000 picoseconds. + Weight::from_parts(42_688_809, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 19_857 - .saturating_add(Weight::from_parts(6_633_799, 0).saturating_mul(x.into())) + // Standard Error: 25_553 + .saturating_add(Weight::from_parts(6_474_373, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -385,8 +385,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `1486` - // Minimum execution time: 13_204_000 picoseconds. - Weight::from_parts(13_544_000, 0) + // Minimum execution time: 11_725_000 picoseconds. + Weight::from_parts(11_947_000, 0) .saturating_add(Weight::from_parts(0, 1486)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -408,8 +408,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `547` // Estimated: `6296` - // Minimum execution time: 92_603_000 picoseconds. - Weight::from_parts(93_224_000, 0) + // Minimum execution time: 91_825_000 picoseconds. + Weight::from_parts(93_938_000, 0) .saturating_add(Weight::from_parts(0, 6296)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(7)) @@ -432,8 +432,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `414` // Estimated: `6296` - // Minimum execution time: 82_675_000 picoseconds. - Weight::from_parts(83_376_000, 0) + // Minimum execution time: 82_364_000 picoseconds. + Weight::from_parts(83_002_000, 0) .saturating_add(Weight::from_parts(0, 6296)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(6)) @@ -443,17 +443,17 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) /// Proof: `DappStaking::TierConfig` (`max_values`: Some(1), `max_size`: Some(91), added: 586, mode: `MaxEncodedLen`) fn on_initialize_voting_to_build_and_earn() -> Weight { // Proof Size summary in bytes: - // Measured: `224` + // Measured: `217` // Estimated: `4254` - // Minimum execution time: 28_436_000 picoseconds. - Weight::from_parts(28_792_000, 0) + // Minimum execution time: 26_215_000 picoseconds. + Weight::from_parts(26_804_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -467,7 +467,7 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) @@ -476,10 +476,10 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn on_initialize_build_and_earn_to_voting() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `319` // Estimated: `4254` - // Minimum execution time: 48_030_000 picoseconds. - Weight::from_parts(49_088_000, 0) + // Minimum execution time: 43_593_000 picoseconds. + Weight::from_parts(44_523_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(7)) @@ -489,7 +489,7 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) @@ -498,29 +498,31 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn on_initialize_build_and_earn_to_build_and_earn() -> Weight { // Proof Size summary in bytes: - // Measured: `278` + // Measured: `271` // Estimated: `4254` - // Minimum execution time: 32_729_000 picoseconds. - Weight::from_parts(33_354_000, 0) + // Minimum execution time: 30_321_000 picoseconds. + Weight::from_parts(30_746_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: `DappStaking::ContractStake` (r:101 w:0) + /// Storage: `DappStaking::ContractStake` (r:17 w:0) /// Proof: `DappStaking::ContractStake` (`max_values`: Some(65535), `max_size`: Some(91), added: 2071, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:0) /// Proof: `DappStaking::TierConfig` (`max_values`: Some(1), `max_size`: Some(91), added: 586, mode: `MaxEncodedLen`) - /// The range of component `x` is `[0, 100]`. + /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) + /// The range of component `x` is `[0, 16]`. fn dapp_tier_assignment(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `97 + x * (32 ±0)` + // Measured: `210 + x * (33 ±0)` // Estimated: `3061 + x * (2071 ±0)` - // Minimum execution time: 7_765_000 picoseconds. - Weight::from_parts(10_543_604, 0) + // Minimum execution time: 10_351_000 picoseconds. + Weight::from_parts(14_594_020, 0) .saturating_add(Weight::from_parts(0, 3061)) - // Standard Error: 3_697 - .saturating_add(Weight::from_parts(2_956_785, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Standard Error: 16_557 + .saturating_add(Weight::from_parts(3_096_286, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(Weight::from_parts(0, 2071).saturating_mul(x.into())) } @@ -534,32 +536,20 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `293` // Estimated: `4254` - // Minimum execution time: 11_087_000 picoseconds. - Weight::from_parts(11_359_000, 0) + // Minimum execution time: 10_116_000 picoseconds. + Weight::from_parts(10_358_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `DappStaking::Ledger` (r:2 w:1) - /// Proof: `DappStaking::Ledger` (`max_values`: None, `max_size`: Some(310), added: 2785, mode: `MaxEncodedLen`) - fn step() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `6560` - // Minimum execution time: 20_705_000 picoseconds. - Weight::from_parts(20_875_000, 0) - .saturating_add(Weight::from_parts(0, 6560)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } /// Storage: `DappStaking::StaticTierParams` (r:0 w:1) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) fn set_static_tier_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_035_000 picoseconds. - Weight::from_parts(11_350_000, 0) + // Minimum execution time: 10_305_000 picoseconds. + Weight::from_parts(10_688_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/shiden/src/genesis_config.rs b/runtime/shiden/src/genesis_config.rs index 6c9443115..b8f911f90 100644 --- a/runtime/shiden/src/genesis_config.rs +++ b/runtime/shiden/src/genesis_config.rs @@ -17,7 +17,10 @@ // along with Astar. If not, see . use crate::*; -use astar_primitives::{evm::EVM_REVERT_CODE, genesis::GenesisAccount, parachain::SHIDEN_ID}; +use astar_primitives::{ + dapp_staking::FIXED_TIER_SLOTS_ARGS, evm::EVM_REVERT_CODE, genesis::GenesisAccount, + parachain::SHIDEN_ID, +}; /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { @@ -46,6 +49,9 @@ pub fn default_config(para_id: u32) -> serde_json::Value { ), ]; + let slots_per_tier = vec![0, 6, 10, 0]; + let tier_rank_multipliers: Vec = vec![0, 24_000, 46_700, 0]; + let authorities = vec![&alice, &bob]; let config = RuntimeGenesisConfig { @@ -114,40 +120,37 @@ pub fn default_config(para_id: u32) -> serde_json::Value { transaction_payment: Default::default(), dapp_staking: DappStakingConfig { reward_portion: vec![ - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_percent(70), Permill::from_percent(30), - Permill::from_percent(20), - Permill::from_percent(10), + Permill::from_percent(0), ], slot_distribution: vec![ - Permill::from_percent(10), - Permill::from_percent(20), - Permill::from_percent(30), - Permill::from_percent(40), + Permill::from_percent(0), + Permill::from_parts(375_000), // 37.5% + Permill::from_parts(625_000), // 62.5% + Permill::from_percent(0), ], - // percentages below are calculated based on a total issuance at the time when dApp staking v3 was launched (84.3M) + // percentages below are calculated based on a total issuance at the time when dApp staking v3 was revamped (8.6B) tier_thresholds: vec![ - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(35_700_000), // 3.57% - minimum_required_percentage: Perbill::from_parts(23_800_000), // 2.38% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(23_200_000), // 2.32% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(8_900_000), // 0.89% - minimum_required_percentage: Perbill::from_parts(6_000_000), // 0.6% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(9_300_000), // 0.93% }, - TierThreshold::DynamicPercentage { - percentage: Perbill::from_parts(2_380_000), // 0.238% - minimum_required_percentage: Perbill::from_parts(1_790_000), // 0.179% - maximum_possible_percentage: Perbill::from_percent(100), + TierThreshold::FixedPercentage { + required_percentage: Perbill::from_parts(3_500_000), // 0.35% }, + // Tier 3: unreachable dummy TierThreshold::FixedPercentage { - required_percentage: Perbill::from_parts(600_000), // 0.06% + required_percentage: Perbill::from_parts(0), // 0% }, ], - slots_per_tier: vec![10, 20, 30, 40], + slots_per_tier, + slot_number_args: FIXED_TIER_SLOTS_ARGS, safeguard: Some(false), + tier_rank_multipliers, ..Default::default() }, inflation: Default::default(), diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index 5316d91f5..b0e245b96 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -450,7 +450,8 @@ impl pallet_dapp_staking::Config for Runtime { type BaseNativeCurrencyPrice = BaseNativeCurrencyPrice; type EraRewardSpanLength = ConstU32<16>; type RewardRetentionInPeriods = ConstU32<3>; - type MaxNumberOfContracts = ConstU32<500>; + type MaxNumberOfContracts = ConstU32<16>; + type MaxNumberOfContractsLegacy = ConstU32<500>; type MaxUnlockingChunks = ConstU32<8>; type MinimumLockedAmount = MinimumStakingAmount; type UnlockingPeriod = ConstU32<4>; @@ -478,15 +479,15 @@ impl pallet_inflation::PayoutPerBlock> for Inflation pub struct InflationCycleConfig; impl CycleConfiguration for InflationCycleConfig { fn periods_per_cycle() -> u32 { - 6 + 2 } fn eras_per_voting_subperiod() -> u32 { - 6 + 1 } fn eras_per_build_and_earn_subperiod() -> u32 { - 55 + 182 } fn blocks_per_era() -> BlockNumber { @@ -1319,15 +1320,18 @@ pub type Executive = frame_executive::Executive< pub type Migrations = (Unreleased, Permanent); /// Unreleased migrations. Add new ones here: -pub type Unreleased = (); +pub type Unreleased = ( + pallet_dapp_staking::migration::versioned_migrations::V10ToV11< + Runtime, + pallet_dapp_staking::migration::DefaultTierParamsV11, + ConstU32<6>, + ConstU32<55>, + >, +); /// Migrations/checks that do not need to be versioned and can run on every upgrade. pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); -parameter_types! { - pub const TierSlotsArgs: (u64, u64) = (100, 50); -} - type EventRecord = frame_system::EventRecord< ::RuntimeEvent, ::Hash, diff --git a/runtime/shiden/src/weights/pallet_dapp_staking.rs b/runtime/shiden/src/weights/pallet_dapp_staking.rs index 5c4cf5983..ab99285af 100644 --- a/runtime/shiden/src/weights/pallet_dapp_staking.rs +++ b/runtime/shiden/src/weights/pallet_dapp_staking.rs @@ -20,7 +20,7 @@ //! Autogenerated weights for `pallet_dapp_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2025-12-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-02-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `gh-runner-01-ovh`, CPU: `Intel(R) Xeon(R) E-2236 CPU @ 3.40GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 @@ -56,8 +56,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_406_000 picoseconds. - Weight::from_parts(9_780_000, 0) + // Minimum execution time: 8_280_000 picoseconds. + Weight::from_parts(8_456_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `DappStaking::IntegratedDApps` (r:1 w:1) @@ -70,8 +70,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `3086` - // Minimum execution time: 17_829_000 picoseconds. - Weight::from_parts(18_261_000, 0) + // Minimum execution time: 16_622_000 picoseconds. + Weight::from_parts(16_904_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -82,8 +82,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 15_690_000 picoseconds. - Weight::from_parts(15_934_000, 0) + // Minimum execution time: 14_462_000 picoseconds. + Weight::from_parts(14_739_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -94,8 +94,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 15_495_000 picoseconds. - Weight::from_parts(15_723_000, 0) + // Minimum execution time: 14_468_000 picoseconds. + Weight::from_parts(14_671_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -110,8 +110,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `97` // Estimated: `3086` - // Minimum execution time: 21_199_000 picoseconds. - Weight::from_parts(21_590_000, 0) + // Minimum execution time: 20_495_000 picoseconds. + Weight::from_parts(20_964_000, 0) .saturating_add(Weight::from_parts(0, 3086)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) @@ -130,8 +130,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `138` // Estimated: `4764` - // Minimum execution time: 39_236_000 picoseconds. - Weight::from_parts(39_812_000, 0) + // Minimum execution time: 38_428_000 picoseconds. + Weight::from_parts(38_798_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -148,8 +148,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `158` // Estimated: `4764` - // Minimum execution time: 39_373_000 picoseconds. - Weight::from_parts(40_217_000, 0) + // Minimum execution time: 38_829_000 picoseconds. + Weight::from_parts(39_266_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -166,8 +166,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `158` // Estimated: `4764` - // Minimum execution time: 36_934_000 picoseconds. - Weight::from_parts(37_581_000, 0) + // Minimum execution time: 35_863_000 picoseconds. + Weight::from_parts(36_365_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -183,13 +183,13 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// The range of component `x` is `[0, 16]`. fn claim_unlocked(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `191` + // Measured: `189` // Estimated: `4764` - // Minimum execution time: 39_285_000 picoseconds. - Weight::from_parts(41_007_215, 0) + // Minimum execution time: 38_247_000 picoseconds. + Weight::from_parts(40_280_703, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 3_865 - .saturating_add(Weight::from_parts(87_249, 0).saturating_mul(x.into())) + // Standard Error: 5_227 + .saturating_add(Weight::from_parts(125_927, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -205,8 +205,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `200` // Estimated: `4764` - // Minimum execution time: 37_235_000 picoseconds. - Weight::from_parts(37_762_000, 0) + // Minimum execution time: 35_717_000 picoseconds. + Weight::from_parts(36_019_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -229,8 +229,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `274` // Estimated: `4764` - // Minimum execution time: 52_461_000 picoseconds. - Weight::from_parts(52_979_000, 0) + // Minimum execution time: 52_590_000 picoseconds. + Weight::from_parts(53_038_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -253,8 +253,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `459` // Estimated: `4764` - // Minimum execution time: 58_337_000 picoseconds. - Weight::from_parts(58_706_000, 0) + // Minimum execution time: 57_593_000 picoseconds. + Weight::from_parts(58_246_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -274,11 +274,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `542` // Estimated: `4764` - // Minimum execution time: 58_460_000 picoseconds. - Weight::from_parts(57_283_269, 0) + // Minimum execution time: 56_652_000 picoseconds. + Weight::from_parts(55_962_135, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 6_506 - .saturating_add(Weight::from_parts(2_028_988, 0).saturating_mul(x.into())) + // Standard Error: 5_616 + .saturating_add(Weight::from_parts(1_951_061, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -295,11 +295,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `519` // Estimated: `4764` - // Minimum execution time: 55_481_000 picoseconds. - Weight::from_parts(54_617_998, 0) + // Minimum execution time: 54_015_000 picoseconds. + Weight::from_parts(52_814_143, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 3_367 - .saturating_add(Weight::from_parts(1_970_584, 0).saturating_mul(x.into())) + // Standard Error: 4_824 + .saturating_add(Weight::from_parts(1_967_369, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -313,8 +313,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `276` // Estimated: `3775` - // Minimum execution time: 46_545_000 picoseconds. - Weight::from_parts(47_014_000, 0) + // Minimum execution time: 44_776_000 picoseconds. + Weight::from_parts(45_395_000, 0) .saturating_add(Weight::from_parts(0, 3775)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,10 +325,10 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn claim_dapp_reward() -> Weight { // Proof Size summary in bytes: - // Measured: `2672` + // Measured: `647` // Estimated: `5113` - // Minimum execution time: 69_501_000 picoseconds. - Weight::from_parts(71_646_000, 0) + // Minimum execution time: 29_711_000 picoseconds. + Weight::from_parts(30_057_000, 0) .saturating_add(Weight::from_parts(0, 5113)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -349,8 +349,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `322` // Estimated: `4764` - // Minimum execution time: 49_089_000 picoseconds. - Weight::from_parts(49_829_000, 0) + // Minimum execution time: 48_660_000 picoseconds. + Weight::from_parts(49_341_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -368,11 +368,11 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `256 + x * (73 ±0)` // Estimated: `4764 + x * (2653 ±0)` - // Minimum execution time: 47_545_000 picoseconds. - Weight::from_parts(44_118_524, 0) + // Minimum execution time: 46_407_000 picoseconds. + Weight::from_parts(43_255_547, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 14_130 - .saturating_add(Weight::from_parts(6_063_445, 0).saturating_mul(x.into())) + // Standard Error: 12_717 + .saturating_add(Weight::from_parts(6_228_978, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -385,8 +385,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `0` // Estimated: `1486` - // Minimum execution time: 12_530_000 picoseconds. - Weight::from_parts(13_183_000, 0) + // Minimum execution time: 11_856_000 picoseconds. + Weight::from_parts(11_948_000, 0) .saturating_add(Weight::from_parts(0, 1486)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -408,8 +408,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `553` // Estimated: `6296` - // Minimum execution time: 93_462_000 picoseconds. - Weight::from_parts(93_840_000, 0) + // Minimum execution time: 91_206_000 picoseconds. + Weight::from_parts(93_356_000, 0) .saturating_add(Weight::from_parts(0, 6296)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(7)) @@ -432,8 +432,8 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `419` // Estimated: `6296` - // Minimum execution time: 82_467_000 picoseconds. - Weight::from_parts(83_472_000, 0) + // Minimum execution time: 81_729_000 picoseconds. + Weight::from_parts(82_562_000, 0) .saturating_add(Weight::from_parts(0, 6296)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(6)) @@ -443,17 +443,17 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) /// Proof: `DappStaking::TierConfig` (`max_values`: Some(1), `max_size`: Some(91), added: 586, mode: `MaxEncodedLen`) fn on_initialize_voting_to_build_and_earn() -> Weight { // Proof Size summary in bytes: - // Measured: `224` + // Measured: `217` // Estimated: `4254` - // Minimum execution time: 29_054_000 picoseconds. - Weight::from_parts(29_408_000, 0) + // Minimum execution time: 25_962_000 picoseconds. + Weight::from_parts(26_436_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) @@ -467,7 +467,7 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) @@ -476,10 +476,10 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn on_initialize_build_and_earn_to_voting() -> Weight { // Proof Size summary in bytes: - // Measured: `764` + // Measured: `872` // Estimated: `4254` - // Minimum execution time: 50_290_000 picoseconds. - Weight::from_parts(51_708_000, 0) + // Minimum execution time: 50_891_000 picoseconds. + Weight::from_parts(51_993_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(7)) @@ -489,7 +489,7 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Storage: `DappStaking::EraRewards` (r:1 w:1) /// Proof: `DappStaking::EraRewards` (`max_values`: None, `max_size`: Some(789), added: 3264, mode: `MaxEncodedLen`) /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) /// Storage: `PriceAggregator::ValuesCircularBuffer` (r:1 w:0) /// Proof: `PriceAggregator::ValuesCircularBuffer` (`max_values`: Some(1), `max_size`: Some(117), added: 612, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:1) @@ -498,29 +498,31 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh /// Proof: `DappStaking::DAppTiers` (`max_values`: None, `max_size`: Some(1648), added: 4123, mode: `MaxEncodedLen`) fn on_initialize_build_and_earn_to_build_and_earn() -> Weight { // Proof Size summary in bytes: - // Measured: `278` + // Measured: `271` // Estimated: `4254` - // Minimum execution time: 32_677_000 picoseconds. - Weight::from_parts(33_491_000, 0) + // Minimum execution time: 29_688_000 picoseconds. + Weight::from_parts(30_217_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: `DappStaking::ContractStake` (r:101 w:0) + /// Storage: `DappStaking::ContractStake` (r:17 w:0) /// Proof: `DappStaking::ContractStake` (`max_values`: Some(65535), `max_size`: Some(91), added: 2071, mode: `MaxEncodedLen`) /// Storage: `DappStaking::TierConfig` (r:1 w:0) /// Proof: `DappStaking::TierConfig` (`max_values`: Some(1), `max_size`: Some(91), added: 586, mode: `MaxEncodedLen`) - /// The range of component `x` is `[0, 100]`. + /// Storage: `DappStaking::StaticTierParams` (r:1 w:0) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) + /// The range of component `x` is `[0, 16]`. fn dapp_tier_assignment(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `97 + x * (32 ±0)` + // Measured: `210 + x * (33 ±0)` // Estimated: `3061 + x * (2071 ±0)` - // Minimum execution time: 7_676_000 picoseconds. - Weight::from_parts(10_555_931, 0) + // Minimum execution time: 10_061_000 picoseconds. + Weight::from_parts(14_133_143, 0) .saturating_add(Weight::from_parts(0, 3061)) - // Standard Error: 8_246 - .saturating_add(Weight::from_parts(3_036_518, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Standard Error: 15_779 + .saturating_add(Weight::from_parts(3_146_293, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(Weight::from_parts(0, 2071).saturating_mul(x.into())) } @@ -534,32 +536,20 @@ impl pallet_dapp_staking::WeightInfo for SubstrateWeigh // Proof Size summary in bytes: // Measured: `293` // Estimated: `4254` - // Minimum execution time: 10_846_000 picoseconds. - Weight::from_parts(11_054_000, 0) + // Minimum execution time: 9_736_000 picoseconds. + Weight::from_parts(9_989_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `DappStaking::Ledger` (r:2 w:1) - /// Proof: `DappStaking::Ledger` (`max_values`: None, `max_size`: Some(310), added: 2785, mode: `MaxEncodedLen`) - fn step() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `6560` - // Minimum execution time: 19_955_000 picoseconds. - Weight::from_parts(20_380_000, 0) - .saturating_add(Weight::from_parts(0, 6560)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } /// Storage: `DappStaking::StaticTierParams` (r:0 w:1) - /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(103), added: 598, mode: `MaxEncodedLen`) + /// Proof: `DappStaking::StaticTierParams` (`max_values`: Some(1), `max_size`: Some(120), added: 615, mode: `MaxEncodedLen`) fn set_static_tier_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_691_000 picoseconds. - Weight::from_parts(10_848_000, 0) + // Minimum execution time: 9_985_000 picoseconds. + Weight::from_parts(10_242_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/tests/integration/src/dapp_staking.rs b/tests/integration/src/dapp_staking.rs index 597c7de16..956cbd5c2 100644 --- a/tests/integration/src/dapp_staking.rs +++ b/tests/integration/src/dapp_staking.rs @@ -19,6 +19,8 @@ #![cfg(test)] use crate::setup::*; +use astar_primitives::dapp_staking::{DAppId, RankedTier, SmartContract}; +use frame_support::traits::BuildGenesisConfig; use sp_runtime::Perquintill; use pallet_collator_selection::{CandidateInfo, Candidates}; @@ -191,3 +193,289 @@ fn no_inflation_rewards_with_zero_decay() { ); }); } + +// During Voting subperiod +// - Initialize TierConfig for the cycle: Tier0/3 disabled, Tier1 has 6 slots, Tier2 has 10 slots (with rank multipliers) +// - Set inflation params with bonus rewards = 0% (fully redistributed away from bonus), force an inflation recalculation, +// and snapshot the active inflation config (used as the “baseline” for the rest of the cycle) +// Collator and Treasury are also set to 0 for no perturbation of the tier threshold estimation. +// - Register 16 dApps and stake so that: +// - Tier1 is fully occupied with ranks [10, 8, 6, 4, 2, 0] (6 dApps) +// - Tier2 is occupied with ranks [9, 8, 7, 6, 5, 4, 3, 2, 1] (9 dApps) +// - 1 extra dApp is staked just below Tier2 threshold and is excluded from tiers (no rewards) +// +// Advance to Build&Earn subperiod +// - Force an era transition into Build&Earn and capture the era that got assigned tiers +// - Ensure TierConfig is carried correctly into the active cycle (slots per tier unchanged) +// - Ensure the dApps occupy the expected tiers and ranks for the assigned era, and the excluded dApp is not in tiers +// - Snapshot inflation active config again and ensure it has NOT recalculated within the same cycle +// +// Still within the same cycle (before the recalculation boundary) +// - Claim dApp rewards for representative Tier1 and Tier2 dApps and verify the emitted event contains the expected tier/rank +// - Ensure double-claim fails and excluded dApp has no claimable rewards +// +// Recalculation boundary (next recalculation era) +// - Force era advancement up to the era right before recalculation and ensure the active inflation config is still the baseline +// - Advance one more era to cross the recalculation boundary and fetch the new active inflation config +// - Ensure recalculation occurred (recalculation_era bumped) and bonus/collator/treasury rewards remain zero +// - Ensure reward pools are >= previous values (issuance increased via minted dApp rewards, so pools should grow or stay equal) + +#[test] +fn full_period_transition_recalculation_and_reward_distribution() { + new_test_ext().execute_with(|| { + // 1. Setup: Tier0/3 empty, Tier1=6 slots, Tier2=10 slots ─── + pallet_dapp_staking::GenesisConfig:: { + slots_per_tier: vec![0, 6, 10, 0], + safeguard: Some(false), + ..Default::default() + } + .build(); + + // Inflation: bonus=treasury=collators=0%, redistributed to dapps + assert_ok!(Inflation::force_set_inflation_params( + RuntimeOrigin::root(), + pallet_inflation::InflationParameters { + max_inflation_rate: Perquintill::from_percent(7), + treasury_part: Perquintill::from_percent(0), + collators_part: Perquintill::from_percent(0), + dapps_part: Perquintill::from_percent(40), + base_stakers_part: Perquintill::from_percent(25), + adjustable_stakers_part: Perquintill::from_percent(35), + bonus_part: Perquintill::zero(), + ideal_staking_rate: Perquintill::from_percent(50), + decay_rate: Perquintill::one(), + }, + )); + assert_ok!(Inflation::force_inflation_recalculation( + RuntimeOrigin::root(), + ActiveProtocolState::::get().era(), + )); + let init_config = pallet_inflation::ActiveInflationConfig::::get(); + assert_eq!( + init_config.bonus_reward_pool_per_period, 0, + "Bonus pool must be zero" + ); + + // ─── 2. Register 16 dApps ─── + let base_id = NextDAppId::::get(); + let contracts: Vec<_> = (0u8..16) + .map(|i| { + let c = AccountId32::new([20 + i; 32]); + assert_ok!(DappStaking::register( + RuntimeOrigin::root(), + ALICE, + SmartContract::Wasm(c.clone()) + )); + c + }) + .collect(); + + let tc = TierConfig::::get(); + let t0 = *tc.tier_thresholds().get(0).unwrap(); + let t1 = *tc.tier_thresholds().get(1).unwrap(); + let t2 = *tc.tier_thresholds().get(2).unwrap(); + + fn stake_for_rank(lo: Balance, hi: Balance, r: u8) -> Balance { + if r == 10 { + return hi; + } + let find_min = |target: u8| -> Balance { + let (mut a, mut b) = (lo, hi); + while a < b { + let m = a + (b - a) / 2; + if RankedTier::find_rank(lo, hi, m) >= target { + b = m; + } else { + a = m + 1; + } + } + a + }; + let s = find_min(r + 1).saturating_sub(1).max(find_min(r)); + assert_eq!(RankedTier::find_rank(lo, hi, s), r); + s + } + + let tier1_ranks: [u8; 6] = [10, 8, 6, 4, 2, 0]; + let tier2_ranks: [u8; 9] = [9, 8, 7, 6, 5, 4, 3, 2, 1]; + + let mut stakes: Vec = tier1_ranks + .iter() + .map(|&r| stake_for_rank(t1, t0, r)) + .collect(); + stakes.extend(tier2_ranks.iter().map(|&r| { + let s = stake_for_rank(t2, t1, r); + assert!(s < t1); + s + })); + stakes.push(t2.saturating_sub(1)); // 16th: excluded + + assert_ok!(DappStaking::lock( + RuntimeOrigin::signed(ALICE.clone()), + stakes.iter().sum() + )); + assert_ok!(DappStaking::lock( + RuntimeOrigin::signed(BOB.clone()), + stakes.iter().sum() + )); + let stakers = [ALICE.clone(), BOB.clone()]; + for (i, &s) in stakes.iter().enumerate() { + assert_ok!(DappStaking::stake( + RuntimeOrigin::signed(stakers[i % 2].clone()), + SmartContract::Wasm(contracts[i].clone()), + s, + )); + } + + // ── 3. Voting → Build&Earn ── + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + assert_eq!( + ActiveProtocolState::::get().subperiod(), + Subperiod::BuildAndEarn + ); + + // ─── 4. Verify TierConfig recalculated correctly ─── + let new_tier_config = TierConfig::::get(); + assert_eq!( + new_tier_config + .slots_per_tier() + .get(0) + .copied() + .unwrap_or(0), + 0 + ); + assert_eq!( + new_tier_config + .slots_per_tier() + .get(1) + .copied() + .unwrap_or(0), + 6 + ); + assert_eq!( + new_tier_config + .slots_per_tier() + .get(2) + .copied() + .unwrap_or(0), + 10 + ); + assert_eq!( + new_tier_config + .slots_per_tier() + .get(3) + .copied() + .unwrap_or(0), + 0 + ); + + // Finalize 1 era for dapps assignation in DAppTiers + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + let assigned_era = ActiveProtocolState::::get().era() - 1; + + // ── 5. Verify tier/rank assignments ── + let mut tiers = DAppTiers::::get(assigned_era).expect("tiers must exist"); + + for (i, &r) in tier1_ranks.iter().enumerate() { + let (amt, ranked) = tiers.try_claim(base_id + i as DAppId).unwrap(); + assert!(amt > 0); + assert_eq!((ranked.tier(), ranked.rank()), (1, r)); + } + for (i, &r) in tier2_ranks.iter().enumerate() { + let (amt, ranked) = tiers.try_claim(base_id + (6 + i) as DAppId).unwrap(); + assert!(amt > 0); + assert_eq!((ranked.tier(), ranked.rank()), (2, r)); + } + assert_eq!( + tiers.try_claim(base_id + 15), + Err(DAppTierError::NoDAppInTiers) + ); + + // ── 6. Inflation stable within cycle ── + assert_eq!( + pallet_inflation::ActiveInflationConfig::::get().recalculation_era, + init_config.recalculation_era, + ); + + // ── 7. Claim rewards BEFORE recalculation ── + let claim_and_check = |idx: usize, exp_tier: u8, exp_rank: u8| { + let sc = SmartContract::Wasm(contracts[idx].clone()); + assert_ok!(DappStaking::claim_dapp_reward( + RuntimeOrigin::signed(ALICE.clone()), + sc.clone(), + assigned_era, + )); + assert!(frame_system::Pallet::::events() + .iter() + .rev() + .any(|r| matches!( + &r.event, + RuntimeEvent::DappStaking( + pallet_dapp_staking::Event::::DAppReward { + smart_contract, + tier_id, + rank, + .. + } + ) if *smart_contract == sc + && *tier_id == exp_tier + && *rank == exp_rank + ))); + }; + + claim_and_check(0, 1, tier1_ranks[0]); + claim_and_check(6, 2, tier2_ranks[0]); + + // Excluded dApp has no rewards + assert_noop!( + DappStaking::claim_dapp_reward( + RuntimeOrigin::signed(ALICE.clone()), + SmartContract::Wasm(contracts[15].clone()), + assigned_era, + ), + pallet_dapp_staking::Error::::NoClaimableRewards + ); + + // ── 8. Force to recalculation boundary ── + while ActiveProtocolState::::get().era() < init_config.recalculation_era - 1 { + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + } + assert_eq!( + pallet_inflation::ActiveInflationConfig::::get().recalculation_era, + init_config.recalculation_era, + "not yet recalculated" + ); + assert_eq!( + pallet_inflation::ActiveInflationConfig::::get().dapp_reward_pool_per_era, + init_config.dapp_reward_pool_per_era, + "no pool inflation" + ); + + assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era)); + run_for_blocks(1); + let new_config = pallet_inflation::ActiveInflationConfig::::get(); + + // ── 9. Verify recalculation happened and pool increased ── + assert!( + new_config.recalculation_era > init_config.recalculation_era, + "Recalculation era must have bumped" + ); + assert_eq!(new_config.bonus_reward_pool_per_period, 0); + assert_eq!(new_config.collator_reward_per_block, 0); + assert_eq!(new_config.treasury_reward_per_block, 0); + + // Rewards were minted via claim_dapp_reward (increasing issuance). + assert!( + new_config.dapp_reward_pool_per_era >= init_config.dapp_reward_pool_per_era, + "dApp reward pool must grow (or stay equal) after recalculation on higher issuance" + ); + assert!( + new_config.base_staker_reward_pool_per_era + >= init_config.base_staker_reward_pool_per_era, + "Base staker pool must grow (or stay equal) after recalculation" + ); + }); +} diff --git a/tests/xcm-simulator/src/mocks/parachain.rs b/tests/xcm-simulator/src/mocks/parachain.rs index 744298f66..93c7b80d5 100644 --- a/tests/xcm-simulator/src/mocks/parachain.rs +++ b/tests/xcm-simulator/src/mocks/parachain.rs @@ -653,6 +653,7 @@ impl pallet_dapp_staking::Config for Runtime { type EraRewardSpanLength = ConstU32<1>; type RewardRetentionInPeriods = ConstU32<2>; type MaxNumberOfContracts = ConstU32<10>; + type MaxNumberOfContractsLegacy = ConstU32<10>; type MaxUnlockingChunks = ConstU32<5>; type MinimumLockedAmount = ConstU128<3>; type UnlockingPeriod = ConstU32<2>;