diff --git a/bin/runtime/src/lib.rs b/bin/runtime/src/lib.rs index 1a4e2a3c64..7ce6e3c0e5 100644 --- a/bin/runtime/src/lib.rs +++ b/bin/runtime/src/lib.rs @@ -42,7 +42,8 @@ use primitives::{ staking::MAX_NOMINATORS_REWARDED_PER_VALIDATOR, wrap_methods, ApiError as AlephApiError, AuraId, AuthorityId as AlephId, Block as AlephBlock, BlockId as AlephBlockId, BlockNumber as AlephBlockNumber, Header as AlephHeader, SessionAuthorityData, SessionCommittee, - SessionIndex, SessionInfoProvider, SessionValidatorError, Version as FinalityVersion, + SessionIndex, SessionInfoProvider, SessionValidatorError, + TotalIssuanceProvider as TotalIssuanceProviderT, Version as FinalityVersion, ADDRESSES_ENCODING, DEFAULT_BAN_REASON_LENGTH, DEFAULT_MAX_WINNERS, DEFAULT_SESSIONS_PER_ERA, DEFAULT_SESSION_PERIOD, MAX_BLOCK_SIZE, MILLISECS_PER_BLOCK, TOKEN, }; @@ -335,6 +336,13 @@ impl SessionInfoProvider for SessionInfoImpl { } } +pub struct TotalIssuanceProvider; +impl TotalIssuanceProviderT for TotalIssuanceProvider { + fn get() -> Balance { + pallet_balances::Pallet::::total_issuance() + } +} + impl pallet_aleph::Config for Runtime { type AuthorityId = AlephId; type RuntimeEvent = RuntimeEvent; @@ -346,6 +354,7 @@ impl pallet_aleph::Config for Runtime { Runtime, >; type NextSessionAuthorityProvider = Session; + type TotalIssuanceProvider = TotalIssuanceProvider; } #[cfg(feature = "liminal")] diff --git a/pallets/aleph/src/lib.rs b/pallets/aleph/src/lib.rs index ce01f3bfb1..fdfe5ecb35 100644 --- a/pallets/aleph/src/lib.rs +++ b/pallets/aleph/src/lib.rs @@ -33,7 +33,7 @@ pub mod pallet { pallet_prelude::{BlockNumberFor, OriginFor}, }; use pallet_session::SessionManager; - use primitives::SessionInfoProvider; + use primitives::{SessionInfoProvider, TotalIssuanceProvider}; use sp_std::collections::btree_set::BTreeSet; #[cfg(feature = "std")] use sp_std::marker::PhantomData; @@ -48,6 +48,7 @@ pub mod pallet { type SessionInfoProvider: SessionInfoProvider>; type SessionManager: SessionManager<::AccountId>; type NextSessionAuthorityProvider: NextSessionAuthorityProvider; + type TotalIssuanceProvider: TotalIssuanceProvider; } #[pallet::event] @@ -242,6 +243,64 @@ pub mod pallet { Self::finality_version() } + + pub fn check_horizon_upper_bound( + new_horizon: u64, + current_horizon: u64, + ) -> Result<(), &'static str> { + match new_horizon > current_horizon.saturating_mul(2).saturating_add(1) { + true => { + Err("Horizon too large, should be at most twice the current value plus one!") + } + false => Ok(()), + } + } + + pub fn check_horizon_lower_bound( + new_horizon: u64, + current_horizon: u64, + ) -> Result<(), &'static str> { + match new_horizon < current_horizon / 2 { + true => Err("Horizon too small, should be at least half the current value!"), + false => Ok(()), + } + } + + pub fn check_azero_cap_upper_bound( + new_cap: Balance, + current_cap: Balance, + total_issuance: Balance, + ) -> Result<(), &'static str> { + let current_gap = current_cap.saturating_sub(total_issuance); + let new_gap = match new_cap.checked_sub(total_issuance) { + Some(new_gap) => new_gap, + None => return Err("AZERO Cap cannot be lower than the current total issuance!"), + }; + match (new_gap > current_gap.saturating_mul(2).saturating_add(1)) + && (new_gap > total_issuance / 128) + { + true => Err("Future issuance too large, should be at most the current total issuance divided by 128, or at most twice the current value plus one!"), + false => Ok(()), + } + } + + pub fn check_azero_cap_lower_bound( + new_cap: Balance, + current_cap: Balance, + total_issuance: Balance, + ) -> Result<(), &'static str> { + let current_gap = current_cap.saturating_sub(total_issuance); + let new_gap = match new_cap.checked_sub(total_issuance) { + Some(new_gap) => new_gap, + None => return Err("AZERO Cap cannot be lower than the current total issuance!"), + }; + match new_gap < current_gap / 2 { + true => { + Err("Future issuance too small, should be at least half the current value!") + } + false => Ok(()), + } + } } #[pallet::call] @@ -298,9 +357,21 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - let azero_cap = azero_cap.unwrap_or_else(AzeroCap::::get); - let horizon_millisecs = - horizon_millisecs.unwrap_or_else(ExponentialInflationHorizon::::get); + let current_azero_cap = AzeroCap::::get(); + let current_horizon_millisecs = ExponentialInflationHorizon::::get(); + let total_issuance = T::TotalIssuanceProvider::get(); + + let azero_cap = azero_cap.unwrap_or(current_azero_cap); + let horizon_millisecs = horizon_millisecs.unwrap_or(current_horizon_millisecs); + + Self::check_horizon_lower_bound(horizon_millisecs, current_horizon_millisecs) + .map_err(DispatchError::Other)?; + Self::check_horizon_upper_bound(horizon_millisecs, current_horizon_millisecs) + .map_err(DispatchError::Other)?; + Self::check_azero_cap_upper_bound(azero_cap, current_azero_cap, total_issuance) + .map_err(DispatchError::Other)?; + Self::check_azero_cap_lower_bound(azero_cap, current_azero_cap, total_issuance) + .map_err(DispatchError::Other)?; AzeroCap::::put(azero_cap); ExponentialInflationHorizon::::put(horizon_millisecs); diff --git a/pallets/aleph/src/mock.rs b/pallets/aleph/src/mock.rs index da026a777b..7c51b62f56 100644 --- a/pallets/aleph/src/mock.rs +++ b/pallets/aleph/src/mock.rs @@ -8,7 +8,9 @@ use frame_support::{ weights::{RuntimeDbWeight, Weight}, }; use frame_system::pallet_prelude::BlockNumberFor; -use primitives::{AuthorityId, SessionInfoProvider}; +use primitives::{ + AuthorityId, SessionInfoProvider, TotalIssuanceProvider as TotalIssuanceProviderT, +}; use sp_core::H256; use sp_runtime::{ impl_opaque_keys, @@ -147,12 +149,20 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } +pub struct TotalIssuanceProvider; +impl TotalIssuanceProviderT for TotalIssuanceProvider { + fn get() -> Balance { + pallet_balances::Pallet::::total_issuance() + } +} + impl Config for Test { type AuthorityId = AuthorityId; type RuntimeEvent = RuntimeEvent; type SessionInfoProvider = SessionInfoImpl; type SessionManager = (); type NextSessionAuthorityProvider = Session; + type TotalIssuanceProvider = TotalIssuanceProvider; } pub fn to_authority(id: &u64) -> AuthorityId { diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 3afa905c99..e81b9f98f1 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -337,6 +337,12 @@ pub trait EraManager { fn on_new_era(era: EraIndex); } +/// Provides the current total issuance. +pub trait TotalIssuanceProvider { + /// Get the current total issuance. + fn get() -> Balance; +} + pub mod staking { use crate::TOKEN;