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

dApp staking v3 - Cycle Alignment #1134

Merged
merged 18 commits into from
Jan 17, 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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 2 additions & 0 deletions chain-extensions/pallet-assets/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ std = [
"assets-chain-extension-types/std",
"pallet-balances/std",
]
try-runtime = ["frame-support/try-runtime"]
runtime-benchmarks = ["pallet-assets/runtime-benchmarks"]
2 changes: 2 additions & 0 deletions chain-extensions/pallet-assets/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ impl pallet_assets::Config for Test {
type CallbackHandle = ();
type Extra = ();
type RemoveItemsLimit = ConstU32<5>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
Expand Down
1 change: 1 addition & 0 deletions pallets/dapp-staking-migration/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ impl pallet_dapp_staking_v3::Config for Test {
type NativePriceProvider = DummyPriceProvider;
type StakingRewardHandler = DummyStakingRewardHandler;
type CycleConfiguration = DummyCycleConfiguration;
type Observers = ();
type EraRewardSpanLength = ConstU32<8>;
type RewardRetentionInPeriods = ConstU32<2>;
type MaxNumberOfContracts = ConstU32<10>;
Expand Down
2 changes: 0 additions & 2 deletions pallets/dapp-staking-v3/rpc/runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ sp-api = { workspace = true }
sp-std = { workspace = true }

astar-primitives = { workspace = true }
pallet-dapp-staking-v3 = { workspace = true }

[features]
default = ["std"]
std = [
"sp-api/std",
"sp-std/std",
"pallet-dapp-staking-v3/std",
"astar-primitives/std",
]
2 changes: 1 addition & 1 deletion pallets/dapp-staking-v3/rpc/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

#![cfg_attr(not(feature = "std"), no_std)]

use astar_primitives::dapp_staking::{DAppId, EraNumber, PeriodNumber, TierId};
use astar_primitives::BlockNumber;
use pallet_dapp_staking_v3::{DAppId, EraNumber, PeriodNumber, TierId};
pub use sp_std::collections::btree_map::BTreeMap;

sp_api::decl_runtime_apis! {
Expand Down
34 changes: 27 additions & 7 deletions pallets/dapp-staking-v3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ use sp_runtime::{
pub use sp_std::vec::Vec;

use astar_primitives::{
dapp_staking::{CycleConfiguration, SmartContractHandle, StakingRewardHandler},
dapp_staking::{
CycleConfiguration, DAppId, EraNumber, Observer as DAppStakingObserver, PeriodNumber,
SmartContractHandle, StakingRewardHandler, TierId,
},
oracle::PriceProvider,
Balance, BlockNumber,
};
Expand Down Expand Up @@ -138,6 +141,9 @@ pub mod pallet {
/// Describes era length, subperiods & period length, as well as cycle length.
type CycleConfiguration: CycleConfiguration;

/// dApp staking event observers, notified when certain events occur.
type Observers: DAppStakingObserver;

/// Maximum length of a single era reward span length entry.
#[pallet::constant]
type EraRewardSpanLength: Get<u32>;
Expand Down Expand Up @@ -1490,16 +1496,12 @@ pub mod pallet {
.into())
}

// TODO: this call should be removed prior to mainnet launch.
// It's super useful for testing purposes, but even though force is used in this pallet & works well,
// it won't apply to the inflation recalculation logic - which is wrong.
// Probably for this call to make sense, an outside logic should handle both inflation & dApp staking state changes.

// TODO: make this unavailable in production, to be only used for testing
/// Used to force a change of era or subperiod.
/// The effect isn't immediate but will happen on the next block.
///
/// Used for testing purposes, when we want to force an era change, or a subperiod change.
/// Not intended to be used in production, except in case of unforseen circumstances.
/// Not intended to be used in production, except in case of unforeseen circumstances.
///
/// Can only be called by manager origin.
#[pallet::call_index(18)]
Expand All @@ -1519,6 +1521,12 @@ pub mod pallet {
state.period_info.next_subperiod_start_era = state.era.saturating_add(1);
}
}

// Right now it won't account for the full weight incurred by calling this notification.
// It's not a big problem since this call is not expected to be called ever in production.
// Also, in case of subperiod forcing, the alignment will be broken but since this is only call for testing,
// we don't need to concern ourselves with it.
Self::notify_block_before_new_era(&state);
});

Self::deposit_event(Event::<T>::Force { forcing_type });
Expand Down Expand Up @@ -1787,6 +1795,12 @@ pub mod pallet {
return consumed_weight;
}

// Inform observers about the upcoming new era, if it's the case.
if protocol_state.next_era_start == now.saturating_add(1) {
consumed_weight
.saturating_accrue(Self::notify_block_before_new_era(&protocol_state));
}

// Nothing to do if it's not new era
if !protocol_state.is_new_era(now) {
return consumed_weight;
Expand Down Expand Up @@ -1943,6 +1957,12 @@ pub mod pallet {
consumed_weight
}

/// Used to notify observers about the upcoming new era in the next block.
fn notify_block_before_new_era(protocol_state: &ProtocolState) -> Weight {
let next_era = protocol_state.era.saturating_add(1);
T::Observers::block_before_new_era(next_era)
}

/// Attempt to cleanup some expired entries, if enough remaining weight & applicable entries exist.
///
/// Returns consumed weight.
Expand Down
16 changes: 15 additions & 1 deletion pallets/dapp-staking-v3/src/test/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ use sp_runtime::{
};
use sp_std::cell::RefCell;

use astar_primitives::{dapp_staking::SmartContract, testing::Header, Balance, BlockNumber};
use astar_primitives::{
dapp_staking::{Observer as DappStakingObserver, SmartContract},
testing::Header,
Balance, BlockNumber,
};

pub(crate) type AccountId = u64;

Expand Down Expand Up @@ -117,6 +121,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 struct DummyStakingRewardHandler;
Expand Down Expand Up @@ -180,6 +185,14 @@ impl CycleConfiguration for DummyCycleConfiguration {
}
}

pub struct DummyDappStakingObserver;
impl DappStakingObserver for DummyDappStakingObserver {
fn block_before_new_era(next_era: EraNumber) -> Weight {
BLOCK_BEFORE_NEW_ERA.with(|v| *v.borrow_mut() = next_era);
Weight::from_parts(1, 2)
}
}

impl pallet_dapp_staking::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeFreezeReason = RuntimeFreezeReason;
Expand All @@ -189,6 +202,7 @@ impl pallet_dapp_staking::Config for Test {
type NativePriceProvider = DummyPriceProvider;
type StakingRewardHandler = DummyStakingRewardHandler;
type CycleConfiguration = DummyCycleConfiguration;
type Observers = DummyDappStakingObserver;
type EraRewardSpanLength = ConstU32<8>;
type RewardRetentionInPeriods = ConstU32<2>;
type MaxNumberOfContracts = ConstU32<10>;
Expand Down
18 changes: 13 additions & 5 deletions pallets/dapp-staking-v3/src/test/testing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ use frame_support::{
use sp_runtime::{traits::Zero, Perbill};
use std::collections::HashMap;

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

/// Helper struct used to store the entire pallet state snapshot.
/// Used when comparison of before/after states is required.
Expand Down Expand Up @@ -700,9 +703,14 @@ pub(crate) fn assert_unstake(
);

let is_loyal = pre_staker_info.is_loyal()
&& !(unstake_subperiod == Subperiod::BuildAndEarn
&& post_staker_info.staked_amount(Subperiod::Voting)
< pre_staker_info.staked_amount(Subperiod::Voting));
&& match unstake_subperiod {
Subperiod::Voting => !post_staker_info.staked_amount(Subperiod::Voting).is_zero(),
Subperiod::BuildAndEarn => {
post_staker_info.staked_amount(Subperiod::Voting)
== pre_staker_info.staked_amount(Subperiod::Voting)
}
};

assert_eq!(
post_staker_info.is_loyal(),
is_loyal,
Expand Down Expand Up @@ -779,7 +787,7 @@ pub(crate) fn assert_claim_staker_rewards(account: AccountId) {
.earliest_staked_era()
.expect("Entry must exist, otherwise 'claim' is invalid.");

// Get the apprropriate era rewards span for the 'first era'
// Get the appropriate era rewards span for the 'first era'
let era_span_length: EraNumber = <Test as Config>::EraRewardSpanLength::get();
let era_span_index = first_claim_era - (first_claim_era % era_span_length);
let era_rewards_span = pre_snapshot
Expand Down
75 changes: 59 additions & 16 deletions pallets/dapp-staking-v3/src/test/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ use frame_support::{
use sp_runtime::traits::Zero;

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

#[test]
fn maintenace_mode_works() {
fn maintenances_mode_works() {
ExtBuilder::build().execute_with(|| {
// Check that maintenance mode is disabled by default
assert!(!ActiveProtocolState::<Test>::get().maintenance);
Expand Down Expand Up @@ -63,7 +63,7 @@ fn maintenace_mode_works() {
}

#[test]
fn maintenace_mode_call_filtering_works() {
fn maintenance_mode_call_filtering_works() {
ExtBuilder::build().execute_with(|| {
// Enable maintenance mode & check post-state
assert_ok!(DappStaking::maintenance_mode(RuntimeOrigin::root(), true));
Expand Down Expand Up @@ -237,7 +237,7 @@ fn on_initialize_base_state_change_works() {
assert_eq!(protocol_state.era, era + 1);
}

// Finaly advance over to the next era and ensure we're back to voting period
// Finally advance over to the next era and ensure we're back to voting period
advance_to_next_era();
let protocol_state = ActiveProtocolState::<Test>::get();
assert_eq!(protocol_state.subperiod(), Subperiod::Voting);
Expand Down Expand Up @@ -356,7 +356,7 @@ fn set_dapp_reward_beneficiary_fails() {
Error::<Test>::ContractNotFound
);

// Non-owner cannnot change reward destination
// Non-owner cannot change reward destination
assert_register(owner, &smart_contract);
assert_noop!(
DappStaking::set_dapp_reward_beneficiary(
Expand Down Expand Up @@ -582,7 +582,7 @@ fn unlock_with_remaining_amount_below_threshold_is_ok() {
}

#[test]
fn unlock_with_amount_higher_than_avaiable_is_ok() {
fn unlock_with_amount_higher_than_available_is_ok() {
ExtBuilder::build().execute_with(|| {
// Lock some amount in a few eras
let account = 2;
Expand Down Expand Up @@ -720,7 +720,7 @@ fn unlock_with_exceeding_unlocking_chunks_storage_limits_fails() {
#[test]
fn withdraw_unbonded_is_ok() {
ExtBuilder::build().execute_with(|| {
// Lock & immediatelly unlock some amount
// Lock & immediately unlock some amount
let account = 2;
let lock_amount = 97;
let unlock_amount = 11;
Expand Down Expand Up @@ -1523,7 +1523,7 @@ fn claim_staker_rewards_fails_due_to_payout_failure() {
// Advance into Build&Earn period, and allow one era to pass.
advance_to_era(ActiveProtocolState::<Test>::get().era + 2);

// Disable successfull reward payout
// Disable successful reward payout
DOES_PAYOUT_SUCCEED.with(|v| *v.borrow_mut() = false);
assert_noop!(
DappStaking::claim_staker_rewards(RuntimeOrigin::signed(account)),
Expand Down Expand Up @@ -1705,7 +1705,7 @@ fn claim_bonus_reward_fails_due_to_payout_failure() {
// Advance to next period so we can claim bonus reward
advance_to_next_period();

// Disable successfull reward payout
// Disable successful reward payout
DOES_PAYOUT_SUCCEED.with(|v| *v.borrow_mut() = false);
assert_noop!(
DappStaking::claim_bonus_reward(RuntimeOrigin::signed(account), smart_contract),
Expand Down Expand Up @@ -1904,7 +1904,7 @@ fn claim_dapp_reward_fails_due_to_payout_failure() {
// Advance 2 eras so we have an entry for reward claiming
advance_to_era(ActiveProtocolState::<Test>::get().era + 2);

// Disable successfull reward payout
// Disable successful reward payout
DOES_PAYOUT_SUCCEED.with(|v| *v.borrow_mut() = false);
assert_noop!(
DappStaking::claim_dapp_reward(
Expand Down Expand Up @@ -2438,11 +2438,6 @@ fn advance_for_some_periods_works() {
})
}

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/////// More complex & composite scenarios, maybe move them into a separate file

#[test]
fn unlock_after_staked_period_ends_is_ok() {
ExtBuilder::build().execute_with(|| {
Expand Down Expand Up @@ -2571,7 +2566,7 @@ fn stake_after_period_ends_with_max_staked_contracts() {
}

#[test]
fn post_unlock_balance_cannot_be_transfered() {
fn post_unlock_balance_cannot_be_transferred() {
ExtBuilder::build().execute_with(|| {
let staker = 2;

Expand Down Expand Up @@ -2628,3 +2623,51 @@ fn post_unlock_balance_cannot_be_transfered() {
assert!(Balances::free_balance(&staker).is_zero());
})
}

#[test]
fn observer_pre_new_era_block_works() {
ExtBuilder::build().execute_with(|| {
fn assert_observer_value(expected: EraNumber) {
BLOCK_BEFORE_NEW_ERA.with(|v| assert_eq!(expected, *v.borrow()));
}

// 1. Sanity check
assert_observer_value(0);

// 2. Advance to the block right before the observer value should be set.
// No modifications should happen.
BLOCK_BEFORE_NEW_ERA.with(|v| {
let _lock = v.borrow();
run_to_block(ActiveProtocolState::<Test>::get().next_era_start - 2);
});

// 3. Advance to the next block, when observer value is expected to be set to the next era.
run_for_blocks(1);
assert_observer_value(2);

// 4. Advance again, until the same similar scenario
BLOCK_BEFORE_NEW_ERA.with(|v| {
let _lock = v.borrow();
run_for_blocks(1);
assert_eq!(
ActiveProtocolState::<Test>::get().subperiod(),
Subperiod::BuildAndEarn,
"Sanity check."
);

run_to_block(ActiveProtocolState::<Test>::get().next_era_start - 2);
assert_eq!(ActiveProtocolState::<Test>::get().era, 2, "Sanity check.");
assert_observer_value(2);
});

// 5. Again, check that value is set to the expected one.
run_for_blocks(1);
assert_observer_value(3);

// 6. Force new era, and ensure observer value is set to the next one.
run_for_blocks(1);
assert_eq!(ActiveProtocolState::<Test>::get().era, 3, "Sanity check.");
assert_ok!(DappStaking::force(RuntimeOrigin::root(), ForcingType::Era));
assert_observer_value(4);
})
}
Loading
Loading