Skip to content

Commit 956f0f0

Browse files
committed
feat: dapp-staking revamp
1 parent e1f96c0 commit 956f0f0

File tree

18 files changed

+1099
-718
lines changed

18 files changed

+1099
-718
lines changed

pallets/dapp-staking/src/benchmarking/mod.rs

Lines changed: 19 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use super::{Pallet as DappStaking, *};
2121
use astar_primitives::Balance;
2222
use frame_benchmarking::v2::*;
2323

24-
use frame_support::{assert_ok, migrations::SteppedMigration, weights::WeightMeter};
24+
use frame_support::assert_ok;
2525
use frame_system::{Pallet as System, RawOrigin};
2626
use sp_std::prelude::*;
2727

@@ -1145,12 +1145,28 @@ mod benchmarks {
11451145
});
11461146
EraRewards::<T>::insert(&cleanup_marker.era_reward_index, reward_span);
11471147

1148+
let rank_points: BoundedVec<
1149+
BoundedVec<u8, ConstU32<15>>,
1150+
T::NumberOfTiers,
1151+
> = (1..=T::NumberOfTiers::get())
1152+
.map(|slots| {
1153+
let inner: BoundedVec<u8, ConstU32<15>> =
1154+
(1..=slots as u8)
1155+
.collect::<Vec<_>>()
1156+
.try_into()
1157+
.expect("Using incremental points; QED.");
1158+
inner
1159+
})
1160+
.collect::<Vec<_>>()
1161+
.try_into()
1162+
.expect("Using `NumberOfTiers` as length; QED.");
1163+
11481164
// Prepare completely filled up tier rewards and insert it into storage.
11491165
DAppTiers::<T>::insert(
11501166
&cleanup_marker.dapp_tiers_index,
11511167
DAppTierRewardsFor::<T> {
11521168
dapps: (0..T::MaxNumberOfContracts::get())
1153-
.map(|dapp_id| (dapp_id as DAppId, RankedTier::new_saturated(0, 0)))
1169+
.map(|dapp_id| (dapp_id as DAppId, RankedTier::new_saturated(0, 0, 10)))
11541170
.collect::<BTreeMap<DAppId, RankedTier>>()
11551171
.try_into()
11561172
.expect("Using `MaxNumberOfContracts` as length; QED."),
@@ -1161,6 +1177,7 @@ mod benchmarks {
11611177
rank_rewards: vec![0; T::NumberOfTiers::get() as usize]
11621178
.try_into()
11631179
.expect("Using `NumberOfTiers` as length; QED."),
1180+
rank_points,
11641181
},
11651182
);
11661183

@@ -1180,73 +1197,6 @@ mod benchmarks {
11801197
);
11811198
}
11821199

1183-
/// Benchmark a single step of mbm migration.
1184-
#[benchmark]
1185-
fn step() {
1186-
let alice: T::AccountId = account("alice", 0, 1);
1187-
1188-
Ledger::<T>::set(
1189-
&alice,
1190-
AccountLedger {
1191-
locked: 1000,
1192-
unlocking: vec![
1193-
UnlockingChunk {
1194-
amount: 100,
1195-
unlock_block: 5,
1196-
},
1197-
UnlockingChunk {
1198-
amount: 100,
1199-
unlock_block: 20,
1200-
},
1201-
]
1202-
.try_into()
1203-
.unwrap(),
1204-
staked: Default::default(),
1205-
staked_future: None,
1206-
contract_stake_count: 0,
1207-
},
1208-
);
1209-
CurrentEraInfo::<T>::put(EraInfo {
1210-
total_locked: 1000,
1211-
unlocking: 200,
1212-
current_stake_amount: Default::default(),
1213-
next_stake_amount: Default::default(),
1214-
});
1215-
1216-
System::<T>::set_block_number(10u32.into());
1217-
let mut meter = WeightMeter::new();
1218-
1219-
#[block]
1220-
{
1221-
crate::migration::LazyMigration::<T, weights::SubstrateWeight<T>>::step(
1222-
None, &mut meter,
1223-
)
1224-
.unwrap();
1225-
}
1226-
1227-
assert_eq!(
1228-
Ledger::<T>::get(&alice),
1229-
AccountLedger {
1230-
locked: 1000,
1231-
unlocking: vec![
1232-
UnlockingChunk {
1233-
amount: 100,
1234-
unlock_block: 5, // already unlocked
1235-
},
1236-
UnlockingChunk {
1237-
amount: 100,
1238-
unlock_block: 30, // double remaining blocks
1239-
},
1240-
]
1241-
.try_into()
1242-
.unwrap(),
1243-
staked: Default::default(),
1244-
staked_future: None,
1245-
contract_stake_count: 0,
1246-
}
1247-
);
1248-
}
1249-
12501200
#[benchmark]
12511201
fn set_static_tier_params() {
12521202
initial_config::<T>();

pallets/dapp-staking/src/benchmarking/utils.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ pub(super) fn init_tier_settings<T: Config>() {
198198
])
199199
.unwrap(),
200200
slot_number_args: STANDARD_TIER_SLOTS_ARGS,
201+
rank_points: BoundedVec::try_from(vec![
202+
BoundedVec::try_from(vec![1u8]).unwrap(),
203+
BoundedVec::try_from(vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).unwrap(),
204+
BoundedVec::try_from(vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).unwrap(),
205+
BoundedVec::try_from(vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).unwrap(),
206+
])
207+
.unwrap(),
208+
base_reward_portion: Permill::from_percent(50),
201209
};
202210

203211
let total_issuance = 1000 * MIN_TIER_THRESHOLD;

pallets/dapp-staking/src/lib.rs

Lines changed: 94 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
3636
#![cfg_attr(not(feature = "std"), no_std)]
3737

38+
extern crate alloc;
39+
40+
use alloc::vec;
41+
pub use alloc::vec::Vec;
3842
use frame_support::{
3943
pallet_prelude::*,
4044
traits::{
@@ -49,13 +53,12 @@ use sp_runtime::{
4953
traits::{One, Saturating, UniqueSaturatedInto, Zero},
5054
Perbill, Permill, SaturatedConversion,
5155
};
52-
pub use sp_std::vec::Vec;
5356

5457
use astar_primitives::{
5558
dapp_staking::{
5659
AccountCheck, CycleConfiguration, DAppId, EraNumber, Observer as DAppStakingObserver,
5760
PeriodNumber, Rank, RankedTier, SmartContractHandle, StakingRewardHandler, TierId,
58-
TierSlots as TierSlotFunc, STANDARD_TIER_SLOTS_ARGS,
61+
TierSlots as TierSlotFunc, MAX_ENCODED_RANK, STANDARD_TIER_SLOTS_ARGS,
5962
},
6063
oracle::PriceProvider,
6164
Balance, BlockNumber,
@@ -94,7 +97,7 @@ pub mod pallet {
9497
use super::*;
9598

9699
/// The current storage version.
97-
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(10);
100+
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(11);
98101

99102
#[pallet::pallet]
100103
#[pallet::storage_version(STORAGE_VERSION)]
@@ -530,6 +533,8 @@ pub mod pallet {
530533
pub slot_number_args: (u64, u64),
531534
pub slots_per_tier: Vec<u16>,
532535
pub safeguard: Option<bool>,
536+
pub rank_points: Vec<Vec<u8>>,
537+
pub base_reward_portion: Permill,
533538
#[serde(skip)]
534539
pub _config: PhantomData<T>,
535540
}
@@ -538,6 +543,15 @@ pub mod pallet {
538543
fn default() -> Self {
539544
use sp_std::vec;
540545
let num_tiers = T::NumberOfTiers::get();
546+
let slots_per_tier = vec![100u16; num_tiers as usize];
547+
let rank_points: Vec<Vec<u8>> = slots_per_tier
548+
.iter()
549+
.map(|&slots| {
550+
let capped = slots.min(MAX_ENCODED_RANK as u16);
551+
(1..=capped as u8).collect()
552+
})
553+
.collect();
554+
541555
Self {
542556
reward_portion: vec![Permill::from_percent(100 / num_tiers); num_tiers as usize],
543557
slot_distribution: vec![Permill::from_percent(100 / num_tiers); num_tiers as usize],
@@ -548,8 +562,10 @@ pub mod pallet {
548562
})
549563
.collect(),
550564
slot_number_args: STANDARD_TIER_SLOTS_ARGS,
551-
slots_per_tier: vec![100; num_tiers as usize],
565+
slots_per_tier,
552566
safeguard: None,
567+
rank_points,
568+
base_reward_portion: Permill::from_percent(50),
553569
_config: Default::default(),
554570
}
555571
}
@@ -558,6 +574,12 @@ pub mod pallet {
558574
#[pallet::genesis_build]
559575
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
560576
fn build(&self) {
577+
let rank_points: Vec<BoundedVec<u8, ConstU32<MAX_ENCODED_RANK>>> = self
578+
.rank_points
579+
.iter()
580+
.map(|inner| BoundedVec::try_from(inner.clone()).expect("Too many rank points"))
581+
.collect();
582+
561583
// Prepare tier parameters & verify their correctness
562584
let tier_params = TierParameters::<T::NumberOfTiers> {
563585
reward_portion: BoundedVec::<Permill, T::NumberOfTiers>::try_from(
@@ -573,6 +595,8 @@ pub mod pallet {
573595
)
574596
.expect("Invalid number of tier thresholds provided."),
575597
slot_number_args: self.slot_number_args,
598+
rank_points: BoundedVec::try_from(rank_points).expect("Too many tiers"),
599+
base_reward_portion: self.base_reward_portion,
576600
};
577601
assert!(
578602
tier_params.is_valid(),
@@ -1882,6 +1906,7 @@ pub mod pallet {
18821906
dapp_stakes.sort_unstable_by(|(_, amount_1), (_, amount_2)| amount_2.cmp(amount_1));
18831907

18841908
let tier_config = TierConfig::<T>::get();
1909+
let tier_params = StaticTierParams::<T>::get();
18851910

18861911
// In case when tier has 1 more free slot, but two dApps with exactly same score satisfy the threshold,
18871912
// 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 +1915,35 @@ pub mod pallet {
18901915
// There is no guarantee this will persist in the future, so it's best for dApps to do their
18911916
// best to avoid getting themselves into such situations.
18921917

1893-
// 3. Calculate rewards.
1894-
let tier_rewards = tier_config
1918+
// 3. Calculate tier allocations
1919+
let tier_allocations: Vec<Balance> = tier_config
18951920
.reward_portion
1921+
.iter()
1922+
.map(|percent| *percent * dapp_reward_pool)
1923+
.collect();
1924+
1925+
// 4. Base rewards = base_reward_portion% of allocation / slots capacity
1926+
let base_portion = tier_params.base_reward_portion;
1927+
let base_rewards_per_tier: Vec<Balance> = tier_allocations
18961928
.iter()
18971929
.zip(tier_config.slots_per_tier.iter())
1898-
.map(|(percent, slots)| {
1899-
if slots.is_zero() {
1930+
.map(|(allocation, capacity)| {
1931+
if capacity.is_zero() {
19001932
Zero::zero()
19011933
} else {
1902-
*percent * dapp_reward_pool / <u16 as Into<Balance>>::into(*slots)
1934+
base_portion
1935+
.mul_floor(*allocation)
1936+
.saturating_div((*capacity).into())
19031937
}
19041938
})
1905-
.collect::<Vec<_>>();
1939+
.collect();
19061940

1907-
// 4.
1941+
// 5.
19081942
// Iterate over configured tier and potential dApps.
19091943
// Each dApp will be assigned to the best possible tier if it satisfies the required condition,
19101944
// and tier capacity hasn't been filled yet.
19111945
let mut dapp_tiers = BTreeMap::new();
1912-
let mut tier_slots = BTreeMap::new();
1913-
19141946
let mut upper_bound = Balance::zero();
1915-
let mut rank_rewards = Vec::new();
19161947

19171948
for (tier_id, (tier_capacity, lower_bound)) in tier_config
19181949
.slots_per_tier
@@ -1929,49 +1960,71 @@ pub mod pallet {
19291960
.take_while(|(_, amount)| amount.ge(lower_bound))
19301961
.take(*tier_capacity as usize)
19311962
{
1963+
let max_rank = tier_params
1964+
.rank_points
1965+
.get(tier_id)
1966+
.map(|v| v.len().saturating_sub(1) as u8) // ranks 0 to len-1, never exceed valid indices
1967+
.unwrap_or(0);
1968+
19321969
let rank = if T::RankingEnabled::get() {
1933-
RankedTier::find_rank(*lower_bound, upper_bound, *staked_amount)
1970+
RankedTier::find_rank(*lower_bound, upper_bound, *staked_amount, max_rank)
19341971
} else {
19351972
0
19361973
};
1937-
tier_slots.insert(*dapp_id, RankedTier::new_saturated(tier_id as u8, rank));
1938-
}
1939-
1940-
// sum of all ranks for this tier
1941-
let ranks_sum = tier_slots
1942-
.iter()
1943-
.fold(0u32, |accum, (_, x)| accum.saturating_add(x.rank().into()));
19441974

1945-
let reward_per_rank = if ranks_sum.is_zero() {
1946-
Balance::zero()
1947-
} else {
1948-
// calculate reward per rank
1949-
let tier_reward = tier_rewards.get(tier_id).copied().unwrap_or_default();
1950-
let empty_slots = tier_capacity.saturating_sub(tier_slots.len() as u16);
1951-
let remaining_reward = tier_reward.saturating_mul(empty_slots.into());
1952-
// make sure required reward doesn't exceed remaining reward
1953-
let reward_per_rank = tier_reward.saturating_div(RankedTier::MAX_RANK.into());
1954-
let expected_reward_for_ranks =
1955-
reward_per_rank.saturating_mul(ranks_sum.into());
1956-
let reward_for_ranks = expected_reward_for_ranks.min(remaining_reward);
1957-
// re-calculate reward per rank based on available reward
1958-
reward_for_ranks.saturating_div(ranks_sum.into())
1959-
};
1975+
let ranked_tier = RankedTier::new_saturated(tier_id as u8, rank, max_rank);
1976+
dapp_tiers.insert(*dapp_id, ranked_tier);
1977+
}
19601978

1961-
rank_rewards.push(reward_per_rank);
1962-
dapp_tiers.append(&mut tier_slots);
19631979
upper_bound = *lower_bound; // current threshold becomes upper bound for next tier
19641980
}
19651981

1966-
// 5.
1982+
// 6. Calculate rank_rewards (reward per rank point for each tier)
1983+
// rank_rewards[tier] = (remainder portion of base% of tier allocation) / sum_of_all_rank_points_in_tier
1984+
let rank_rewards: Vec<Balance> = tier_allocations
1985+
.iter()
1986+
.zip(tier_config.slots_per_tier.iter())
1987+
.enumerate()
1988+
.map(|(tier_id, (allocation, slots))| {
1989+
// If tier has no slots, no rank rewards can be claimed
1990+
if slots.is_zero() {
1991+
return Zero::zero();
1992+
}
1993+
1994+
// Sum ALL configured rank_points for this tier
1995+
let total_points: u32 = tier_params
1996+
.rank_points
1997+
.get(tier_id)
1998+
.map(|points| points.iter().map(|&p| p as u32).sum())
1999+
.unwrap_or(0);
2000+
2001+
if total_points.is_zero() {
2002+
Zero::zero()
2003+
} else {
2004+
let rank_portion =
2005+
Permill::one().saturating_sub(tier_params.base_reward_portion);
2006+
let rank_pool = rank_portion.mul_floor(*allocation);
2007+
rank_pool.saturating_div(total_points.into())
2008+
}
2009+
})
2010+
.collect();
2011+
2012+
let rank_points_vec: Vec<Vec<u8>> = tier_params
2013+
.rank_points
2014+
.iter()
2015+
.map(|inner_bv| inner_bv.clone().into_inner())
2016+
.collect();
2017+
2018+
// 7.
19672019
// Prepare and return tier & rewards info.
19682020
// In case rewards creation fails, we just write the default value. This should never happen though.
19692021
(
19702022
DAppTierRewards::<T::MaxNumberOfContracts, T::NumberOfTiers>::new(
19712023
dapp_tiers,
1972-
tier_rewards,
2024+
base_rewards_per_tier,
19732025
period,
19742026
rank_rewards,
2027+
rank_points_vec,
19752028
)
19762029
.unwrap_or_default(),
19772030
counter,

0 commit comments

Comments
 (0)