From 1fcbed552222c55be927e8132af85d623a213671 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 6 Oct 2023 16:36:45 +0200 Subject: [PATCH] Era & Period change logic --- pallets/dapp-staking-v3/src/lib.rs | 91 +++++++++++++++++++++++- pallets/dapp-staking-v3/src/test/mock.rs | 3 + pallets/dapp-staking-v3/src/types.rs | 39 ++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index f253fe6a0b..423ee9ecb4 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -45,7 +45,7 @@ use frame_support::{ weights::Weight, }; use frame_system::pallet_prelude::*; -use sp_runtime::traits::{BadOrigin, Saturating, Zero}; +use sp_runtime::traits::{BadOrigin, One, Saturating, Zero}; use astar_primitives::Balance; @@ -89,6 +89,21 @@ pub mod pallet { /// Privileged origin for managing dApp staking pallet. type ManagerOrigin: EnsureOrigin<::RuntimeOrigin>; + /// Length of era in block numbers. + #[pallet::constant] + type EraLength: Get; + + /// Length of the `Voting` period in eras. + /// Although `Voting` period only consumes one 'era', we still measure it's length in eras + /// for the sake of simplicity & consistency. + #[pallet::constant] + type VotingPeriodLength: Get; + + /// Length of the `Build&Earn` period in eras. + /// Each `Build&Earn` period consists of one or more distinct eras. + #[pallet::constant] + type BuildAndEarnPeriodLength: Get; + /// Maximum number of contracts that can be integrated into dApp staking at once. /// TODO: maybe this can be reworded or improved later on - but we want a ceiling! #[pallet::constant] @@ -123,6 +138,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { + /// New era has started. + NewEra { era: EraNumber }, + /// New period has started. + NewPeriod { + period_type: PeriodType, + number: PeriodNumber, + }, /// A smart contract has been registered for dApp staking DAppRegistered { owner: T::AccountId, @@ -262,6 +284,73 @@ pub mod pallet { #[pallet::storage] pub type CurrentEraInfo = StorageValue<_, EraInfo, ValueQuery>; + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + let mut protocol_state = ActiveProtocolState::::get(); + + // We should not modify pallet storage while in maintenance mode. + // This is a safety measure, since maintenance mode is expected to be + // enabled in case some misbehavior or corrupted storage is detected. + if protocol_state.maintenance { + return T::DbWeight::get().reads(1); + } + + if protocol_state.is_new_era(now) { + let next_era = protocol_state.era.saturating_add(1); + let maybe_period_event = match protocol_state.period_type() { + PeriodType::Voting => { + // For the sake of consistency + let ending_era = + next_era.saturating_add(T::BuildAndEarnPeriodLength::get()); + let build_and_earn_start_block = now.saturating_add(T::EraLength::get()); + protocol_state.next_period(ending_era, build_and_earn_start_block); + + Some(Event::::NewPeriod { + period_type: protocol_state.period_type(), + number: protocol_state.period_number(), + }) + } + PeriodType::BuildAndEarn => { + // TODO: trigger reward calculation here. This will be implemented later. + + // Switch to `Voting` period if conditions are met. + if protocol_state.period_info.is_ending(next_era) { + // For the sake of consistency + let ending_era = next_era.saturating_add(1); + let voting_period_length = T::EraLength::get() + .saturating_mul(T::VotingPeriodLength::get().into()); + let voting_start_block = now + .saturating_add(voting_period_length) + .saturating_add(One::one()); + protocol_state.next_period(ending_era, voting_start_block); + + // TODO: trigger tier configuration calculation based on internal & external params. + + Some(Event::::NewPeriod { + period_type: protocol_state.period_type(), + number: protocol_state.period_number(), + }) + } else { + None + } + } + }; + + protocol_state.era = next_era; + ActiveProtocolState::::put(protocol_state); + + Self::deposit_event(Event::::NewEra { era: next_era }); + if let Some(period_event) = maybe_period_event { + Self::deposit_event(period_event); + } + } + + // TODO: benchmark later + T::DbWeight::get().reads_writes(1, 1) + } + } + #[pallet::call] impl Pallet { /// Used to enable or disable maintenance mode. diff --git a/pallets/dapp-staking-v3/src/test/mock.rs b/pallets/dapp-staking-v3/src/test/mock.rs index f0cfb42ce1..487202e833 100644 --- a/pallets/dapp-staking-v3/src/test/mock.rs +++ b/pallets/dapp-staking-v3/src/test/mock.rs @@ -109,6 +109,9 @@ impl pallet_dapp_staking::Config for Test { type Currency = Balances; type SmartContract = MockSmartContract; type ManagerOrigin = frame_system::EnsureRoot; + type EraLength = ConstU64<8>; + type VotingPeriodLength = ConstU32<2>; + type BuildAndEarnPeriodLength = ConstU32<4>; type MaxNumberOfContracts = ConstU16<10>; type MaxLockedChunks = ConstU32<5>; type MaxUnlockingChunks = ConstU32<5>; diff --git a/pallets/dapp-staking-v3/src/types.rs b/pallets/dapp-staking-v3/src/types.rs index ad3cad2050..db1ad2f669 100644 --- a/pallets/dapp-staking-v3/src/types.rs +++ b/pallets/dapp-staking-v3/src/types.rs @@ -235,6 +235,15 @@ pub enum PeriodType { BuildAndEarn, } +impl PeriodType { + fn next(&self) -> Self { + match self { + PeriodType::Voting => PeriodType::BuildAndEarn, + PeriodType::BuildAndEarn => PeriodType::Voting, + } + } +} + /// Wrapper type around current `PeriodType` and era number when it's expected to end. #[derive(Encode, Decode, MaxEncodedLen, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] pub struct PeriodInfo { @@ -306,6 +315,36 @@ where } } +impl ProtocolState +where + BlockNumber: AtLeast32BitUnsigned + MaxEncodedLen, +{ + /// TODO + pub fn period_type(&self) -> PeriodType { + self.period_info.period_type + } + + /// TODO + pub fn period_number(&self) -> PeriodNumber { + self.period_info.number + } + + /// TODO + pub fn is_new_era(&self, now: BlockNumber) -> bool { + self.next_era_start <= now + } + + /// TODO + pub fn next_period(&mut self, ending_era: EraNumber, next_era_start: BlockNumber) { + self.period_info = PeriodInfo { + number: self.period_number(), + period_type: self.period_type().next(), + ending_era, + }; + self.next_era_start = next_era_start; + } +} + /// dApp state in which some dApp is in. #[derive(Encode, Decode, MaxEncodedLen, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] pub enum DAppState {