Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Base native currency price for dApp Staking #1252

Merged
merged 6 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have assert for number of slots as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean to use to calculate the expected number of slots and compare it with the one in the tier config? (if yes, no problem)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant assert_eq!(TierConfig::<Test>::get().number_of_slots, 120)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but the 120 should be derived from somewhere, right?

I've added the check below this one, but kept the old one to ensure change is in the right direction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking hardcoded number so you can visually verify change. number > base_number can be any number

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formula for calculating number of slots is provided via an associated type, it's not dictated by the dAPp staking pallet itself.

This is why, as per your suggestion, I added this below the existing > check:

        assert_eq!(
            TierConfig::<Test>::get().number_of_slots,
            <Test as Config>::TierSlots::number_of_slots(higher_price),
        );

Only it's not hardcoded but derived from the associated type function call.

"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
Loading