Skip to content

Commit

Permalink
Tier reward calculation WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinonard committed Oct 20, 2023
1 parent b25ccb1 commit 3d2d146
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 46 deletions.
93 changes: 83 additions & 10 deletions pallets/dapp-staking-v3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use frame_support::{
weights::Weight,
};
use frame_system::pallet_prelude::*;
use sp_arithmetic::fixed_point::FixedU64;
use sp_runtime::{
traits::{BadOrigin, Saturating, Zero},
Perbill,
Expand Down Expand Up @@ -135,6 +136,10 @@ pub mod pallet {
/// Minimum amount staker can stake on a contract.
#[pallet::constant]
type MinimumStakeAmount: Get<Balance>;

/// Number of different tiers.
#[pallet::constant]
type NumberOfTiers: Get<u32>;
}

#[pallet::event]
Expand Down Expand Up @@ -285,6 +290,7 @@ pub mod pallet {
#[pallet::storage]
pub type NextDAppId<T: Config> = StorageValue<_, DAppId, ValueQuery>;

// TODO: where to track TierLabels? E.g. a label to bootstrap a dApp into a specific tier.
/// Map of all dApps integrated into dApp staking protocol.
#[pallet::storage]
pub type IntegratedDApps<T: Config> = CountedStorageMap<
Expand Down Expand Up @@ -339,6 +345,21 @@ pub mod pallet {
pub type PeriodEnd<T: Config> =
StorageMap<_, Twox64Concat, PeriodNumber, PeriodEndInfo, OptionQuery>;

/// Static tier parameters used to calculate tier configuration.
#[pallet::storage]
pub type StaticTierParams<T: Config> =
StorageValue<_, TierParameters<T::NumberOfTiers>, ValueQuery>;

/// Tier configuration to be used during the newly started period
#[pallet::storage]
pub type NextTierConfig<T: Config> =
StorageValue<_, TierConfiguration<T::NumberOfTiers>, ValueQuery>;

/// Tier configuration user for current & preceding eras.
#[pallet::storage]
pub type TierConfig<T: Config> =
StorageValue<_, TierConfiguration<T::NumberOfTiers>, ValueQuery>;

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(now: BlockNumberFor<T>) -> Weight {
Expand All @@ -363,8 +384,11 @@ pub mod pallet {
let (maybe_period_event, era_reward) = match protocol_state.period_type() {
PeriodType::Voting => {
// For the sake of consistency, we put zero reward into storage
let era_reward =
EraReward::new(Balance::zero(), era_info.total_staked_amount());
let era_reward = EraReward {
staker_reward_pool: Balance::zero(),
staked: era_info.total_staked_amount(),
dapp_reward_pool: Balance::zero(),
};

let ending_era =
next_era.saturating_add(T::StandardErasPerBuildAndEarnPeriod::get());
Expand All @@ -374,6 +398,10 @@ pub mod pallet {

era_info.migrate_to_next_era(Some(protocol_state.period_type()));

// Update tier configuration to be used when calculating rewards for the upcoming eras
let next_tier_config = NextTierConfig::<T>::take();
TierConfig::<T>::put(next_tier_config);

(
Some(Event::<T>::NewPeriod {
period_type: protocol_state.period_type(),
Expand All @@ -383,11 +411,15 @@ pub mod pallet {
)
}
PeriodType::BuildAndEarn => {
// TODO: trigger dAPp tier reward calculation here. This will be implemented later.
// TODO: trigger dApp tier reward calculation here. This will be implemented later.

let staker_reward_pool = Balance::from(1_000_000_000_000u128); // TODO: calculate this properly, inject it from outside (Tokenomics 2.0 pallet?)
let era_reward =
EraReward::new(staker_reward_pool, era_info.total_staked_amount());
let dapp_reward_pool = Balance::from(1_000_000_000u128); // TODO: same as above
let era_reward = EraReward {
staker_reward_pool,
staked: era_info.total_staked_amount(),
dapp_reward_pool,
};

// Switch to `Voting` period if conditions are met.
if protocol_state.period_info.is_next_period(next_era) {
Expand All @@ -412,7 +444,12 @@ pub mod pallet {

era_info.migrate_to_next_era(Some(protocol_state.period_type()));

// TODO: trigger tier configuration calculation based on internal & external params.
// Re-calculate tier configuration for the upcoming new period
let tier_params = StaticTierParams::<T>::get();
let average_price = FixedU64::from_rational(1, 10); // TODO: implement price fetching later
let new_tier_config =
TierConfig::<T>::get().calculate_new(average_price, &tier_params);
NextTierConfig::<T>::put(new_tier_config);

(
Some(Event::<T>::NewPeriod {
Expand Down Expand Up @@ -1083,11 +1120,11 @@ pub mod pallet {
.ok_or(Error::<T>::InternalClaimStakerError)?;

// Optimization, and zero-division protection
if amount.is_zero() || era_reward.staked().is_zero() {
if amount.is_zero() || era_reward.staked.is_zero() {
continue;
}
let staker_reward = Perbill::from_rational(amount, era_reward.staked())
* era_reward.staker_reward_pool();
let staker_reward = Perbill::from_rational(amount, era_reward.staked)
* era_reward.staker_reward_pool;

rewards.push((era, staker_reward));
reward_sum.saturating_accrue(staker_reward);
Expand Down Expand Up @@ -1234,7 +1271,7 @@ pub mod pallet {
}

/// `true` if smart contract is active, `false` if it has been unregistered.
fn is_active(smart_contract: &T::SmartContract) -> bool {
pub fn is_active(smart_contract: &T::SmartContract) -> bool {
IntegratedDApps::<T>::get(smart_contract)
.map_or(false, |dapp_info| dapp_info.state == DAppState::Registered)
}
Expand All @@ -1243,5 +1280,41 @@ pub mod pallet {
pub fn era_reward_span_index(era: EraNumber) -> EraNumber {
era.saturating_sub(era % T::EraRewardSpanLength::get())
}

// TODO - by breaking this into multiple steps, if they are too heavy for a single block, we can distribute them between multiple blocks.
pub fn dapp_tier_assignment(era: EraNumber, period: PeriodNumber) {
let tier_config = TierConfig::<T>::get();

Check failure on line 1286 in pallets/dapp-staking-v3/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

unused variable: `tier_config`

error: unused variable: `tier_config` --> pallets/dapp-staking-v3/src/lib.rs:1286:17 | 1286 | let tier_config = TierConfig::<T>::get(); | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_tier_config` | = note: `-D unused-variables` implied by `-D warnings`
// TODO: this really looks ugly, and too complicated. Botom line is, this value has to exist. If it doesn't we have to assume it's `Default`.
// Rewards will just end up being all zeroes.
let reward_info = EraRewards::<T>::get(Self::era_reward_span_index(era))

Check failure on line 1289 in pallets/dapp-staking-v3/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

unused variable: `reward_info`

error: unused variable: `reward_info` --> pallets/dapp-staking-v3/src/lib.rs:1289:17 | 1289 | let reward_info = EraRewards::<T>::get(Self::era_reward_span_index(era)) | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_reward_info`
.map(|span| span.get(era).map(|x| *x).unwrap_or_default())
.unwrap_or_default();

let mut dapp_stakes = Vec::with_capacity(IntegratedDApps::<T>::count() as usize);

// 1.
// This is bounded by max amount of dApps we allow to be registered
for (smart_contract, dapp_info) in IntegratedDApps::<T>::iter() {
// Skip unregistered dApps
if dapp_info.state != DAppState::Registered {
continue;
}

// Skip dApps which don't have ANY amount staked (TODO: potential improvement is to prune all dApps below minimum threshold)
let stake_amount = match ContractStake::<T>::get(&smart_contract).get(era, period) {
Some(stake_amount) if !stake_amount.total().is_zero() => stake_amount,
_ => continue,
};

// TODO: maybe also push the 'Label' here?
dapp_stakes.push((dapp_info.id, stake_amount.total()));
}

// 2.
// Sort by amount staked, in reverse - top dApp will end in the first place, 0th index.
dapp_stakes.sort_unstable_by(|(_, amount_1), (_, amount_2)| amount_2.cmp(amount_1));

// TODO: continue here
}
}
}
39 changes: 38 additions & 1 deletion pallets/dapp-staking-v3/src/test/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
Permill,
};

use sp_io::TestExternalities;
Expand Down Expand Up @@ -119,6 +120,7 @@ impl pallet_dapp_staking::Config for Test {
type MinimumLockedAmount = ConstU128<MINIMUM_LOCK_AMOUNT>;
type UnlockingPeriod = ConstU64<20>;
type MinimumStakeAmount = ConstU128<3>;
type NumberOfTiers = ConstU32<4>;
}

// TODO: why not just change this to e.g. u32 for test?
Expand Down Expand Up @@ -174,8 +176,43 @@ impl ExtBuilder {
maintenance: false,
});

// TODO: improve this laterm should be handled via genesis?
let tier_params = TierParameters::<<Test as Config>::NumberOfTiers> {
reward_portion: BoundedVec::try_from(vec![
Permill::from_percent(40),
Permill::from_percent(30),
Permill::from_percent(20),
Permill::from_percent(10),
])
.unwrap(),
slot_distribution: BoundedVec::try_from(vec![
Permill::from_percent(10),
Permill::from_percent(20),
Permill::from_percent(30),
Permill::from_percent(40),
])
.unwrap(),
tier_thresholds: BoundedVec::try_from(vec![
TierThreshold::DynamicTvlAmount { amount: 1000 },
TierThreshold::DynamicTvlAmount { amount: 500 },
TierThreshold::DynamicTvlAmount { amount: 100 },
TierThreshold::FixedTvlAmount { amount: 50 },
])
.unwrap(),
};
let init_tier_config = TierConfiguration::<<Test as Config>::NumberOfTiers> {
number_of_slots: 100,
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(),
};

pallet_dapp_staking::StaticTierParams::<Test>::put(tier_params);
pallet_dapp_staking::TierConfig::<Test>::put(init_tier_config);

// DappStaking::on_initialize(System::block_number());
});
}
);

ext
}
Expand Down
4 changes: 2 additions & 2 deletions pallets/dapp-staking-v3/src/test/testing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,8 +781,8 @@ pub(crate) fn assert_claim_staker_rewards(account: AccountId) {
.get(era)
.expect("Entry must exist, otherwise 'claim' is invalid.");

let reward = Perbill::from_rational(amount, era_reward_info.staked())
* era_reward_info.staker_reward_pool();
let reward = Perbill::from_rational(amount, era_reward_info.staked)
* era_reward_info.staker_reward_pool;
if reward.is_zero() {
continue;
}
Expand Down
22 changes: 17 additions & 5 deletions pallets/dapp-staking-v3/src/test/tests_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,15 +1469,23 @@ fn era_reward_span_push_and_get_works() {

// Insert some values and verify state change
let era_1 = 5;
let era_reward_1 = EraReward::new(23, 41);
let era_reward_1 = EraReward {
staker_reward_pool: 23,
staked: 41,
dapp_reward_pool: 17,
};
assert!(era_reward_span.push(era_1, era_reward_1).is_ok());
assert_eq!(era_reward_span.len(), 1);
assert_eq!(era_reward_span.first_era(), era_1);
assert_eq!(era_reward_span.last_era(), era_1);

// Insert another value and verify state change
let era_2 = era_1 + 1;
let era_reward_2 = EraReward::new(37, 53);
let era_reward_2 = EraReward {
staker_reward_pool: 37,
staked: 53,
dapp_reward_pool: 19,
};
assert!(era_reward_span.push(era_2, era_reward_2).is_ok());
assert_eq!(era_reward_span.len(), 2);
assert_eq!(era_reward_span.first_era(), era_1);
Expand All @@ -1496,7 +1504,11 @@ fn era_reward_span_fails_when_expected() {

// Push first values to get started
let era_1 = 5;
let era_reward = EraReward::new(23, 41);
let era_reward = EraReward {
staker_reward_pool: 23,
staked: 41,
dapp_reward_pool: 17,
};
assert!(era_reward_span.push(era_1, era_reward).is_ok());

// Attempting to push incorrect era results in an error
Expand All @@ -1521,7 +1533,7 @@ fn era_reward_span_fails_when_expected() {
fn tier_slot_configuration_basic_tests() {
// TODO: this should be expanded & improved later
get_u32_type!(TiersNum, 4);
let params = TierSlotParameters::<TiersNum> {
let params = TierParameters::<TiersNum> {
reward_portion: BoundedVec::try_from(vec![
Permill::from_percent(40),
Permill::from_percent(30),
Expand All @@ -1547,7 +1559,7 @@ fn tier_slot_configuration_basic_tests() {
assert!(params.is_valid(), "Example params must be valid!");

// Create a configuration with some values
let init_config = TierSlotConfiguration::<TiersNum> {
let init_config = TierConfiguration::<TiersNum> {
number_of_slots: 100,
slots_per_tier: BoundedVec::try_from(vec![10, 20, 30, 40]).unwrap(),
reward_portion: params.reward_portion.clone(),
Expand Down
Loading

0 comments on commit 3d2d146

Please sign in to comment.