Skip to content

Commit

Permalink
dynamic tier thresholds (#1306)
Browse files Browse the repository at this point in the history
* remove number_of_slots from TiersConfiguration

* remove use of TierThreshold enum in TiersConfiguration

* percentage variants added to TierThreshold for values derivation from total issuance

* dyn tier percentages adjusted

* storage migration

* try-runtime sanity check

* README updated

* tvl amount variants removed from TierThreshold

* versioned migration used for static tier params

* PR review fixes
  • Loading branch information
ipapandinas authored Aug 6, 2024
1 parent 1344b7e commit 6f34256
Show file tree
Hide file tree
Showing 17 changed files with 643 additions and 280 deletions.
24 changes: 13 additions & 11 deletions bin/collator/src/local/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use sc_service::ChainType;
use sp_core::{crypto::Ss58Codec, sr25519, Pair, Public};
use sp_runtime::{
traits::{AccountIdConversion, IdentifyAccount, Verify},
Permill,
Perbill, Permill,
};

type AccountPublic = <Signature as Verify>::Signer;
Expand Down Expand Up @@ -163,19 +163,21 @@ fn testnet_genesis(
Permill::from_percent(40),
],
tier_thresholds: vec![
TierThreshold::DynamicTvlAmount {
amount: 100 * AST,
minimum_amount: 80 * AST,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(35_700_000), // 3.57%
minimum_required_percentage: Perbill::from_parts(23_800_000), // 2.38%
},
TierThreshold::DynamicTvlAmount {
amount: 50 * AST,
minimum_amount: 40 * AST,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(8_900_000), // 0.89%
minimum_required_percentage: Perbill::from_parts(6_000_000), // 0.6%
},
TierThreshold::DynamicTvlAmount {
amount: 20 * AST,
minimum_amount: 20 * AST,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(23_800_000), // 2.38%
minimum_required_percentage: Perbill::from_parts(17_900_000), // 1.79%
},
TierThreshold::FixedPercentage {
required_percentage: Perbill::from_parts(600_000), // 0.06%
},
TierThreshold::FixedTvlAmount { amount: 10 * AST },
],
slots_per_tier: vec![10, 20, 30, 40],
safeguard: Some(false),
Expand Down
25 changes: 13 additions & 12 deletions bin/collator/src/parachain/chain_spec/astar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use sc_service::ChainType;
use sp_core::{sr25519, Pair, Public};
use sp_runtime::{
traits::{IdentifyAccount, Verify},
Permill,
Perbill, Permill,
};

const PARA_ID: u32 = 2006;
Expand Down Expand Up @@ -167,21 +167,22 @@ fn make_genesis(
Permill::from_percent(30),
Permill::from_percent(40),
],
// percentages below are calulated based on total issuance at the time when dApp staking v3 was launched (8.4B)
tier_thresholds: vec![
TierThreshold::DynamicTvlAmount {
amount: 30000 * ASTR,
minimum_amount: 20000 * ASTR,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(35_700_000), // 3.57%
minimum_required_percentage: Perbill::from_parts(23_800_000), // 2.38%
},
TierThreshold::DynamicTvlAmount {
amount: 7500 * ASTR,
minimum_amount: 5000 * ASTR,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(8_900_000), // 0.89%
minimum_required_percentage: Perbill::from_parts(6_000_000), // 0.6%
},
TierThreshold::DynamicTvlAmount {
amount: 20000 * ASTR,
minimum_amount: 15000 * ASTR,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(2_380_000), // 0.238%
minimum_required_percentage: Perbill::from_parts(1_790_000), // 0.179%
},
TierThreshold::FixedTvlAmount {
amount: 5000 * ASTR,
TierThreshold::FixedPercentage {
required_percentage: Perbill::from_parts(200_000), // 0.02%
},
],
slots_per_tier: vec![10, 20, 30, 40],
Expand Down
25 changes: 14 additions & 11 deletions bin/collator/src/parachain/chain_spec/shibuya.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use sp_core::{sr25519, Pair, Public};
use astar_primitives::oracle::CurrencyAmount;
use sp_runtime::{
traits::{IdentifyAccount, Verify},
Permill,
Perbill, Permill,
};

use super::{get_from_seed, Extensions};
Expand Down Expand Up @@ -179,20 +179,23 @@ fn make_genesis(
Permill::from_percent(30),
Permill::from_percent(40),
],
// percentages below are calulated based on a total issuance at the time when dApp staking v3 was launched (147M)
tier_thresholds: vec![
TierThreshold::DynamicTvlAmount {
amount: 100 * SBY,
minimum_amount: 80 * SBY,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(20_000), // 0.0020%
minimum_required_percentage: Perbill::from_parts(17_000), // 0.0017%
},
TierThreshold::DynamicTvlAmount {
amount: 50 * SBY,
minimum_amount: 40 * SBY,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(13_000), // 0.0013%
minimum_required_percentage: Perbill::from_parts(10_000), // 0.0010%
},
TierThreshold::DynamicTvlAmount {
amount: 20 * SBY,
minimum_amount: 20 * SBY,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(5_400), // 0.00054%
minimum_required_percentage: Perbill::from_parts(3_400), // 0.00034%
},
TierThreshold::FixedPercentage {
required_percentage: Perbill::from_parts(1_400), // 0.00014%
},
TierThreshold::FixedTvlAmount { amount: 10 * SBY },
],
slots_per_tier: vec![10, 20, 30, 40],
safeguard: Some(false),
Expand Down
25 changes: 14 additions & 11 deletions bin/collator/src/parachain/chain_spec/shiden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use sp_core::{sr25519, Pair, Public};
use astar_primitives::oracle::CurrencyAmount;
use sp_runtime::{
traits::{IdentifyAccount, Verify},
Permill,
Perbill, Permill,
};

use super::{get_from_seed, Extensions};
Expand Down Expand Up @@ -169,20 +169,23 @@ fn make_genesis(
Permill::from_percent(30),
Permill::from_percent(40),
],
// percentages below are calulated based on a total issuance at the time when dApp staking v3 was launched (84.3M)
tier_thresholds: vec![
TierThreshold::DynamicTvlAmount {
amount: 30000 * SDN,
minimum_amount: 20000 * SDN,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(35_700_000), // 3.57%
minimum_required_percentage: Perbill::from_parts(23_800_000), // 2.38%
},
TierThreshold::DynamicTvlAmount {
amount: 7500 * SDN,
minimum_amount: 5000 * SDN,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(8_900_000), // 0.89%
minimum_required_percentage: Perbill::from_parts(6_000_000), // 0.6%
},
TierThreshold::DynamicTvlAmount {
amount: 20000 * SDN,
minimum_amount: 15000 * SDN,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(2_380_000), // 0.238%
minimum_required_percentage: Perbill::from_parts(1_790_000), // 0.179%
},
TierThreshold::FixedPercentage {
required_percentage: Perbill::from_parts(600_000), // 0.06%
},
TierThreshold::FixedTvlAmount { amount: 5000 * SDN },
],
slots_per_tier: vec![10, 20, 30, 40],
safeguard: Some(false),
Expand Down
2 changes: 1 addition & 1 deletion pallets/dapp-staking-v3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ At the end of each build&earn subperiod era, dApps are evaluated using a simple
Based on this metric, they are sorted, and assigned to tiers.

There is a limited number of tiers, and each tier has a limited capacity of slots.
Each tier also has a _threshold_ which a dApp must satisfy in order to enter it.
Each tier also has a _threshold_ which a dApp must satisfy in order to enter it. Thresholds for each tier are dynamically calculated as percentages of the total issuance at the time of the dApp staking v3 launch.

Better tiers bring bigger rewards, so dApps are encouraged to compete for higher tiers and attract staker's support.
For each tier, the reward pool and capacity are fixed. Each dApp within a tier always gets the same amount of reward.
Expand Down
3 changes: 1 addition & 2 deletions pallets/dapp-staking-v3/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,10 +704,9 @@ mod benchmarks {
// This is a hacky part to ensure we accommodate max number of contracts.
TierConfig::<T>::mutate(|config| {
let max_number_of_contracts: u16 = T::MaxNumberOfContracts::get().try_into().unwrap();
config.number_of_slots = max_number_of_contracts;
config.slots_per_tier[0] = max_number_of_contracts;
config.slots_per_tier[1..].iter_mut().for_each(|x| *x = 0);
config.tier_thresholds[0] = TierThreshold::FixedTvlAmount { amount: 1 };
config.tier_thresholds[0] = 1;
});
force_advance_to_next_era::<T>();
let claim_era = ActiveProtocolState::<T>::get().era - 1;
Expand Down
34 changes: 21 additions & 13 deletions pallets/dapp-staking-v3/src/benchmarking/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,32 +177,40 @@ pub(super) fn init_tier_settings<T: Config>() {
])
.unwrap(),
tier_thresholds: BoundedVec::try_from(vec![
TierThreshold::DynamicTvlAmount {
amount: 100 * UNIT,
minimum_amount: 80 * UNIT,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(11_112_000), // 1.1112%
minimum_required_percentage: Perbill::from_parts(8_889_000), // 0.8889%
},
TierThreshold::DynamicTvlAmount {
amount: 50 * UNIT,
minimum_amount: 40 * UNIT,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(5_556_000), // 0.5556%
minimum_required_percentage: Perbill::from_parts(4_400_000), // 0.44%
},
TierThreshold::DynamicTvlAmount {
amount: 20 * UNIT,
minimum_amount: 20 * UNIT,
TierThreshold::DynamicPercentage {
percentage: Perbill::from_parts(2_223_000), // 0.2223%
minimum_required_percentage: Perbill::from_parts(2_223_000), // 0.2223%
},
TierThreshold::FixedTvlAmount {
amount: MIN_TIER_THRESHOLD,
TierThreshold::FixedPercentage {
required_percentage: Perbill::from_parts(1_667_000), // 0.1667%
},
])
.unwrap(),
};

let total_issuance = 1000 * MIN_TIER_THRESHOLD;
let tier_thresholds = tier_params
.tier_thresholds
.iter()
.map(|t| t.threshold(total_issuance))
.collect::<Vec<Balance>>()
.try_into()
.expect("Invalid number of tier thresholds provided.");

// Init tier config, based on the initial params
let init_tier_config =
TiersConfiguration::<T::NumberOfTiers, T::TierSlots, T::BaseNativeCurrencyPrice> {
number_of_slots: NUMBER_OF_SLOTS.try_into().unwrap(),
slots_per_tier: BoundedVec::try_from(vec![10, 20, 30, 40]).unwrap(),
reward_portion: tier_params.reward_portion.clone(),
tier_thresholds: tier_params.tier_thresholds.clone(),
tier_thresholds,
_phantom: Default::default(),
};

Expand Down
52 changes: 34 additions & 18 deletions pallets/dapp-staking-v3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub mod pallet {
use super::*;

/// The current storage version.
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(7);
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(8);

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
Expand Down Expand Up @@ -520,8 +520,9 @@ pub mod pallet {
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)
.map(|i| TierThreshold::FixedTvlAmount {
amount: (10 * i).into(),
.rev()
.map(|i| TierThreshold::FixedPercentage {
required_percentage: Perbill::from_percent(i),
})
.collect(),
slots_per_tier: vec![100; num_tiers as usize],
Expand Down Expand Up @@ -554,23 +555,27 @@ pub mod pallet {
"Invalid tier parameters values provided."
);

// Prepare tier configuration and verify its correctness
let number_of_slots = self.slots_per_tier.iter().fold(0_u16, |acc, &slots| {
acc.checked_add(slots).expect("Overflow")
});
let total_issuance = T::Currency::total_issuance();
let tier_thresholds = tier_params
.tier_thresholds
.iter()
.map(|t| t.threshold(total_issuance))
.collect::<Vec<Balance>>()
.try_into()
.expect("Invalid number of tier thresholds provided.");

let tier_config =
TiersConfiguration::<T::NumberOfTiers, T::TierSlots, T::BaseNativeCurrencyPrice> {
number_of_slots,
slots_per_tier: BoundedVec::<u16, T::NumberOfTiers>::try_from(
self.slots_per_tier.clone(),
)
.expect("Invalid number of slots per tier entries provided."),
reward_portion: tier_params.reward_portion.clone(),
tier_thresholds: tier_params.tier_thresholds.clone(),
tier_thresholds,
_phantom: Default::default(),
};
assert!(
tier_params.is_valid(),
tier_config.is_valid(),
"Invalid tier config values provided."
);

Expand Down Expand Up @@ -1717,25 +1722,23 @@ pub mod pallet {
let mut upper_bound = Balance::zero();
let mut rank_rewards = Vec::new();

for (tier_id, (tier_capacity, tier_threshold)) in tier_config
for (tier_id, (tier_capacity, lower_bound)) in tier_config
.slots_per_tier
.iter()
.zip(tier_config.tier_thresholds.iter())
.enumerate()
{
let lower_bound = tier_threshold.threshold();

// 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)
for (dapp_id, staked_amount) in dapp_stakes
.iter()
.skip(dapp_tiers.len())
.take_while(|(_, amount)| tier_threshold.is_satisfied(*amount))
.take_while(|(_, amount)| amount.ge(lower_bound))
.take(*tier_capacity as usize)
{
let rank = if T::RankingEnabled::get() {
RankedTier::find_rank(lower_bound, upper_bound, *staked_amount)
RankedTier::find_rank(*lower_bound, upper_bound, *staked_amount)
} else {
0
};
Expand Down Expand Up @@ -1765,7 +1768,7 @@ pub mod pallet {

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

// 5.
Expand Down Expand Up @@ -1954,8 +1957,21 @@ pub mod pallet {
// Re-calculate tier configuration for the upcoming new era
let tier_params = StaticTierParams::<T>::get();
let average_price = T::NativePriceProvider::average_price();
let new_tier_config = TierConfig::<T>::get().calculate_new(average_price, &tier_params);
TierConfig::<T>::put(new_tier_config);
let total_issuance = T::Currency::total_issuance();

let new_tier_config =
TierConfig::<T>::get().calculate_new(&tier_params, average_price, total_issuance);

// Validate new tier configuration
if new_tier_config.is_valid() {
TierConfig::<T>::put(new_tier_config);
} else {
log::warn!(
target: LOG_TARGET,
"New tier configuration is invalid for era {}, preserving old one.",
next_era
);
}

Self::deposit_event(Event::<T>::NewEra { era: next_era });
if let Some(period_event) = maybe_period_event {
Expand Down
Loading

0 comments on commit 6f34256

Please sign in to comment.