Skip to content

Commit

Permalink
Base native currency price for dApp Staking (#1252)
Browse files Browse the repository at this point in the history
* Base native currency price for dApp Staking

* Remove redundant condition check

* Version bumps

* Comments

* Comment

* Remove migration code
  • Loading branch information
Dinonard committed May 28, 2024
1 parent 33be0de commit 111d18f
Show file tree
Hide file tree
Showing 18 changed files with 212 additions and 91 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bin/collator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "astar-collator"
version = "5.39.0"
version = "5.39.1"
description = "Astar collator implementation in Rust."
build = "build.rs"
default-run = "astar-collator"
Expand Down
15 changes: 8 additions & 7 deletions pallets/dapp-staking-v3/src/benchmarking/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,14 @@ pub(super) fn init_tier_settings<T: Config>() {
};

// Init tier config, based on the initial params
let init_tier_config = TiersConfiguration::<T::NumberOfTiers, T::TierSlots> {
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(),
_phantom: Default::default(),
};
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(),
_phantom: Default::default(),
};

assert!(tier_params.is_valid());
assert!(init_tier_config.is_valid());
Expand Down
40 changes: 28 additions & 12 deletions pallets/dapp-staking-v3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use frame_support::{
weights::Weight,
};
use frame_system::pallet_prelude::*;
use sp_arithmetic::fixed_point::FixedU128;
use sp_runtime::{
traits::{BadOrigin, One, Saturating, UniqueSaturatedInto, Zero},
Perbill, Permill, SaturatedConversion,
Expand Down Expand Up @@ -148,6 +149,17 @@ pub mod pallet {
/// Used to calculate total number of tier slots for some price.
type TierSlots: TierSlotFunc;

/// Base native currency price used to calculate base number of slots.
/// This is used to adjust tier configuration, tier thresholds specifically, based on the native token price changes.
///
/// When dApp staking thresholds were modeled, a base price was set from which the initial configuration is derived.
/// E.g. for a price of 0.05$, we get 100 slots, and certain tier thresholds.
/// Using these values as the base, we can adjust the configuration based on the current price.
///
/// This is connected with the `TierSlots` associated type, since it's used to calculate the total number of slots for the given price.
#[pallet::constant]
type BaseNativeCurrencyPrice: Get<FixedU128>;

/// Maximum length of a single era reward span length entry.
#[pallet::constant]
type EraRewardSpanLength: Get<u32>;
Expand Down Expand Up @@ -446,8 +458,11 @@ pub mod pallet {

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

/// Information about which tier a dApp belonged to in a specific era.
#[pallet::storage]
Expand Down Expand Up @@ -509,16 +524,17 @@ pub mod pallet {
let number_of_slots = self.slots_per_tier.iter().fold(0_u16, |acc, &slots| {
acc.checked_add(slots).expect("Overflow")
});
let tier_config = TiersConfiguration::<T::NumberOfTiers, T::TierSlots> {
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(),
_phantom: Default::default(),
};
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(),
_phantom: Default::default(),
};
assert!(
tier_params.is_valid(),
"Invalid tier config values provided."
Expand Down
8 changes: 7 additions & 1 deletion pallets/dapp-staking-v3/src/test/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl PriceProvider for DummyPriceProvider {
thread_local! {
pub(crate) static DOES_PAYOUT_SUCCEED: RefCell<bool> = RefCell::new(false);
pub(crate) static BLOCK_BEFORE_NEW_ERA: RefCell<EraNumber> = RefCell::new(0);
pub(crate) static NATIVE_PRICE: RefCell<FixedU128> = RefCell::new(FixedU128::from_rational(1, 10));
pub(crate) static NATIVE_PRICE: RefCell<FixedU128> = RefCell::new(BaseNativeCurrencyPrice::get());
}

pub struct DummyStakingRewardHandler;
Expand Down Expand Up @@ -195,6 +195,10 @@ impl AccountCheck<AccountId> for DummyAccountCheck {
}
}

parameter_types! {
pub const BaseNativeCurrencyPrice: FixedU128 = FixedU128::from_rational(5, 100);
}

impl pallet_dapp_staking::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeFreezeReason = RuntimeFreezeReason;
Expand All @@ -207,6 +211,7 @@ impl pallet_dapp_staking::Config for Test {
type Observers = DummyDappStakingObserver;
type AccountCheck = DummyAccountCheck;
type TierSlots = StandardTierSlots;
type BaseNativeCurrencyPrice = BaseNativeCurrencyPrice;
type EraRewardSpanLength = ConstU32<8>;
type RewardRetentionInPeriods = ConstU32<2>;
type MaxNumberOfContracts = ConstU32<10>;
Expand Down Expand Up @@ -315,6 +320,7 @@ impl ExtBuilder {
let init_tier_config = TiersConfiguration::<
<Test as Config>::NumberOfTiers,
<Test as Config>::TierSlots,
<Test as Config>::BaseNativeCurrencyPrice,
> {
number_of_slots: 40,
slots_per_tier: BoundedVec::try_from(vec![2, 5, 13, 20]).unwrap(),
Expand Down
100 changes: 99 additions & 1 deletion pallets/dapp-staking-v3/src/test/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use frame_support::{
use sp_runtime::{traits::Zero, FixedU128};

use astar_primitives::{
dapp_staking::{CycleConfiguration, EraNumber, SmartContractHandle},
dapp_staking::{CycleConfiguration, EraNumber, SmartContractHandle, TierSlots},
Balance, BlockNumber,
};

Expand Down Expand Up @@ -3022,3 +3022,101 @@ fn safeguard_configurable_by_genesis_config() {
assert!(Safeguard::<Test>::get());
});
}

#[test]
fn base_number_of_slots_is_respected() {
ExtBuilder::build().execute_with(|| {
// 0. Get expected number of slots for the base price
let base_native_price = <Test as Config>::BaseNativeCurrencyPrice::get();
let base_number_of_slots = <Test as Config>::TierSlots::number_of_slots(base_native_price);

// 1. Make sure base native price is set initially and calculate the new config. Store the thresholds for later comparison.
NATIVE_PRICE.with(|v| *v.borrow_mut() = base_native_price);
assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era));
run_for_blocks(1);

assert_eq!(
TierConfig::<Test>::get().number_of_slots,
base_number_of_slots,
"Base number of slots is expected for base native currency price."
);

let base_thresholds = TierConfig::<Test>::get().tier_thresholds;

// 2. Increase the price significantly, and ensure number of slots has increased, and thresholds have been saturated.
let higher_price = base_native_price * FixedU128::from(1000);
NATIVE_PRICE.with(|v| *v.borrow_mut() = higher_price);
assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era));
run_for_blocks(1);

assert!(
TierConfig::<Test>::get().number_of_slots > base_number_of_slots,
"Price has increased, therefore number of slots must increase."
);
assert_eq!(
TierConfig::<Test>::get().number_of_slots,
<Test as Config>::TierSlots::number_of_slots(higher_price),
);

for tier_threshold in TierConfig::<Test>::get().tier_thresholds.iter() {
if let TierThreshold::DynamicTvlAmount {
amount,
minimum_amount,
} = tier_threshold
{
assert_eq!(*amount, *minimum_amount, "Thresholds must be saturated.");
}
}

// 3. Bring it back down to the base price, and expect number of slots to be the same as the base number of slots,
// and thresholds to be the same as the base thresholds.
NATIVE_PRICE.with(|v| *v.borrow_mut() = base_native_price);
assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era));
run_for_blocks(1);

assert_eq!(
TierConfig::<Test>::get().number_of_slots,
base_number_of_slots,
"Base number of slots is expected for base native currency price."
);

assert_eq!(
TierConfig::<Test>::get().tier_thresholds,
base_thresholds,
"Thresholds must be the same as the base thresholds."
);

// 4. Bring it below the base price, and expect number of slots to decrease.
let lower_price = base_native_price * FixedU128::from_rational(1, 1000);
NATIVE_PRICE.with(|v| *v.borrow_mut() = lower_price);
assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era));
run_for_blocks(1);

assert!(
TierConfig::<Test>::get().number_of_slots < base_number_of_slots,
"Price has decreased, therefore number of slots must decrease."
);
assert_eq!(
TierConfig::<Test>::get().number_of_slots,
<Test as Config>::TierSlots::number_of_slots(lower_price),
);

// 5. Bring it back to the base price, and expect number of slots to be the same as the base number of slots,
// and thresholds to be the same as the base thresholds.
NATIVE_PRICE.with(|v| *v.borrow_mut() = base_native_price);
assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era));
run_for_blocks(1);

assert_eq!(
TierConfig::<Test>::get().number_of_slots,
base_number_of_slots,
"Base number of slots is expected for base native currency price."
);

assert_eq!(
TierConfig::<Test>::get().tier_thresholds,
base_thresholds,
"Thresholds must be the same as the base thresholds."
);
})
}
7 changes: 5 additions & 2 deletions pallets/dapp-staking-v3/src/test/tests_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// along with Astar. If not, see <http://www.gnu.org/licenses/>.

use astar_primitives::{dapp_staking::StandardTierSlots, Balance};
use frame_support::assert_ok;
use frame_support::{assert_ok, parameter_types};
use sp_arithmetic::fixed_point::FixedU128;
use sp_runtime::Permill;

Expand Down Expand Up @@ -2877,7 +2877,10 @@ fn tier_configuration_basic_tests() {
assert!(params.is_valid(), "Example params must be valid!");

// Create a configuration with some values
let init_config = TiersConfiguration::<TiersNum, StandardTierSlots> {
parameter_types! {
pub const BaseNativeCurrencyPrice: FixedU128 = FixedU128::from_rational(5, 100);
}
let init_config = TiersConfiguration::<TiersNum, StandardTierSlots, BaseNativeCurrencyPrice> {
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 111d18f

Please sign in to comment.