diff --git a/Cargo.lock b/Cargo.lock index 37f31856..6b2c12ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7694,6 +7694,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "hex-literal 0.3.4", "inflation-manager", "log", "pallet-balances", diff --git a/node/src/parachain/dev_chain_spec.rs b/node/src/parachain/dev_chain_spec.rs index 01396957..7c20bd21 100644 --- a/node/src/parachain/dev_chain_spec.rs +++ b/node/src/parachain/dev_chain_spec.rs @@ -1,19 +1,17 @@ use crate::parachain::Extensions; use cumulus_primitives_core::ParaId; use peaq_dev_runtime::{ - staking, BalancesConfig, BlockRewardConfig, CouncilConfig, EVMConfig, EthereumConfig, - GenesisAccount, MorConfig, ParachainInfoConfig, ParachainStakingConfig, PeaqMorConfig, - PeaqPrecompiles, Runtime, RuntimeGenesisConfig, SudoConfig, WASM_BINARY, + staking, BalancesConfig, BlockRewardConfig, BlockRewardMigrationSinks, CouncilConfig, + EVMConfig, EthereumConfig, GenesisAccount, MorConfig, ParachainInfoConfig, + ParachainStakingConfig, PeaqMorConfig, PeaqPrecompiles, Runtime, RuntimeGenesisConfig, + SudoConfig, WASM_BINARY, }; use peaq_primitives_xcm::{AccountId, Balance, Signature}; use runtime_common::{CENTS, DOLLARS, MILLICENTS, TOKEN_DECIMALS}; use sc_service::{ChainType, Properties}; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{sr25519, Pair, Public}; -use sp_runtime::{ - traits::{IdentifyAccount, Verify}, - Perbill, -}; +use sp_runtime::traits::{IdentifyAccount, Verify}; /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec; @@ -125,15 +123,10 @@ fn configure_genesis( }, inflation_manager: Default::default(), block_reward: BlockRewardConfig { - // Make sure sum is 100 - reward_config: pallet_block_reward::RewardDistributionConfig { - treasury_percent: Perbill::from_percent(25), - collators_delegators_percent: Perbill::from_percent(40), - coretime_percent: Perbill::from_percent(10), - subsidization_pool_percent: Perbill::from_percent(5), - depin_staking_percent: Perbill::from_percent(5), - depin_incentivization_percent: Perbill::from_percent(15), - }, + // Same sinks the live chain's `MigrateToV3x` migration adopts -- see + // `BlockRewardMigrationSinks` in runtime/peaq-dev/src/lib.rs, the single + // place that decides this runtime's post-migration distribution. + sinks: BlockRewardMigrationSinks::get(), _phantom: Default::default(), }, vesting: Default::default(), diff --git a/node/src/parachain/krest_chain_spec.rs b/node/src/parachain/krest_chain_spec.rs index b7626355..e0cc43bc 100644 --- a/node/src/parachain/krest_chain_spec.rs +++ b/node/src/parachain/krest_chain_spec.rs @@ -1,15 +1,14 @@ use crate::parachain::Extensions; use cumulus_primitives_core::ParaId; use peaq_krest_runtime::{ - staking, BalancesConfig, BlockRewardConfig, CouncilConfig, EVMConfig, EthereumConfig, - GenesisAccount, ParachainInfoConfig, ParachainStakingConfig, PeaqPrecompiles, Runtime, - RuntimeGenesisConfig, SudoConfig, WASM_BINARY, + staking, BalancesConfig, BlockRewardConfig, BlockRewardMigrationSinks, CouncilConfig, + EVMConfig, EthereumConfig, GenesisAccount, ParachainInfoConfig, ParachainStakingConfig, + PeaqPrecompiles, Runtime, RuntimeGenesisConfig, SudoConfig, WASM_BINARY, }; use peaq_primitives_xcm::{AccountId, Balance}; use runtime_common::TOKEN_DECIMALS; use sc_service::{ChainType, Properties}; use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_runtime::Perbill; use crate::parachain::dev_chain_spec::{authority_keys_from_seed, get_account_id_from_seed}; @@ -110,15 +109,10 @@ fn configure_genesis( }, inflation_manager: Default::default(), block_reward: BlockRewardConfig { - // Make sure sum is 100 - reward_config: pallet_block_reward::RewardDistributionConfig { - treasury_percent: Perbill::from_percent(25), - collators_delegators_percent: Perbill::from_percent(40), - coretime_percent: Perbill::from_percent(10), - subsidization_pool_percent: Perbill::from_percent(5), - depin_staking_percent: Perbill::from_percent(5), - depin_incentivization_percent: Perbill::from_percent(15), - }, + // Same sinks the live chain's `MigrateToV3x` migration adopts -- see + // `BlockRewardMigrationSinks` in runtime/krest/src/lib.rs, the single + // place that decides this runtime's post-migration distribution. + sinks: BlockRewardMigrationSinks::get(), _phantom: Default::default(), }, vesting: Default::default(), diff --git a/node/src/parachain/peaq_chain_spec.rs b/node/src/parachain/peaq_chain_spec.rs index 13cd5bbc..c77993f5 100644 --- a/node/src/parachain/peaq_chain_spec.rs +++ b/node/src/parachain/peaq_chain_spec.rs @@ -2,14 +2,13 @@ use crate::parachain::Extensions; use cumulus_primitives_core::ParaId; use peaq_primitives_xcm::{AccountId, Balance}; use peaq_runtime::{ - staking, BalancesConfig, BlockRewardConfig, CouncilConfig, EVMConfig, EthereumConfig, - GenesisAccount, ParachainInfoConfig, ParachainStakingConfig, PeaqPrecompiles, Runtime, - RuntimeGenesisConfig, SudoConfig, WASM_BINARY, + staking, BalancesConfig, BlockRewardConfig, BlockRewardMigrationSinks, CouncilConfig, + EVMConfig, EthereumConfig, GenesisAccount, ParachainInfoConfig, ParachainStakingConfig, + PeaqPrecompiles, Runtime, RuntimeGenesisConfig, SudoConfig, WASM_BINARY, }; use runtime_common::TOKEN_DECIMALS; use sc_service::{ChainType, Properties}; use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_runtime::Perbill; use crate::parachain::dev_chain_spec::{authority_keys_from_seed, get_account_id_from_seed}; @@ -114,15 +113,10 @@ fn configure_genesis( }, inflation_manager: Default::default(), block_reward: BlockRewardConfig { - // Make sure sum is 100 - reward_config: pallet_block_reward::RewardDistributionConfig { - treasury_percent: Perbill::from_percent(25), - collators_delegators_percent: Perbill::from_percent(40), - coretime_percent: Perbill::from_percent(10), - subsidization_pool_percent: Perbill::from_percent(5), - depin_staking_percent: Perbill::from_percent(5), - depin_incentivization_percent: Perbill::from_percent(15), - }, + // Same sinks the live chain's `MigrateToV3x` migration adopts -- see + // `BlockRewardMigrationSinks` in runtime/peaq/src/lib.rs, the single + // place that decides this runtime's post-migration distribution. + sinks: BlockRewardMigrationSinks::get(), _phantom: Default::default(), }, vesting: Default::default(), diff --git a/pallets/address-unification/src/lib.rs b/pallets/address-unification/src/lib.rs index cbb8c8b2..d3081bc0 100644 --- a/pallets/address-unification/src/lib.rs +++ b/pallets/address-unification/src/lib.rs @@ -46,7 +46,7 @@ use sp_runtime::{ }; use sp_std::{marker::PhantomData, vec::Vec}; -mod convert_impl; +pub mod convert_impl; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; diff --git a/pallets/block-reward/Cargo.toml b/pallets/block-reward/Cargo.toml index eab1e1bf..533423d0 100644 --- a/pallets/block-reward/Cargo.toml +++ b/pallets/block-reward/Cargo.toml @@ -18,14 +18,15 @@ pallet-balances = { workspace = true, default-features = false, features = ["ins pallet-timestamp = { workspace = true, default-features = false, optional = true } scale-info = { workspace = true, default-features = false, features = ["derive"] } serde = { workspace = true, default-features = false } -sp-core = { workspace = true, default-features = false, optional = true } +sp-core = { workspace = true, default-features = false } sp-runtime = { workspace = true, default-features = false } sp-std = { workspace = true, default-features = false } -inflation-manager ={ path = "../inflation-manager", default-features = false} +inflation-manager = { path = "../inflation-manager", default-features = false} peaq-primitives-xcm = { path = "../../primitives/xcm", default-features = false } [dev-dependencies] sp-io = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = false } [features] default = ["std"] diff --git a/pallets/block-reward/src/benchmarking.rs b/pallets/block-reward/src/benchmarking.rs index 9280f1a2..2b30cbfa 100644 --- a/pallets/block-reward/src/benchmarking.rs +++ b/pallets/block-reward/src/benchmarking.rs @@ -3,21 +3,59 @@ use super::*; use frame_benchmarking::v1::{benchmarks, impl_benchmark_test_suite}; +use frame_support::traits::{Currency, Imbalance}; use frame_system::{Pallet as System, RawOrigin}; +use sp_core::H160; +use sp_runtime::Perbill; +use sp_std::vec::Vec; /// Assert that the last event equals the provided one. fn assert_last_event(generic_event: ::RuntimeEvent) { System::::assert_last_event(generic_event.into()); } +/// Builds `n` sinks whose shares sum to exactly 100%, each pointing at a distinct +/// EVM address so `T::AddressMapping` resolves them to `n` distinct accounts. +fn make_sinks(n: u32) -> Vec { + const ACCURACY: u32 = 1_000_000_000; // Perbill::ACCURACY + let base = ACCURACY / n; + let remainder = ACCURACY % n; + (0..n) + .map(|i| Sink { + target: RewardTarget::Evm(H160::from_low_u64_be(u64::from(i) + 1)), + share: Perbill::from_parts(base + u32::from(i < remainder)), + }) + .collect() +} + benchmarks! { - set_configuration { - let reward_config = RewardDistributionConfig::default(); - assert!(reward_config.is_consistent()); - }: _(RawOrigin::Root, reward_config.clone()) + set_sinks { + let n in 1 .. T::MaxSinks::get(); + let sinks = make_sinks(n); + let bounded: SinksOf = SinksOf::::try_from(sinks.clone()).unwrap(); + }: _(RawOrigin::Root, bounded) + verify { + assert_last_event::(Event::::TokenSinksUpdated { sinks }.into()); + } + + distribute_imbalances { + let n in 1 .. T::MaxSinks::get(); + let sinks = make_sinks(n); + let bounded: SinksOf = SinksOf::::try_from(sinks).unwrap(); + for sink in bounded.iter() { + frame_system::Pallet::::inc_providers(&Pallet::::resolve(&sink.target)); + } + Sinks::::put(&bounded); + + let amount = BalanceOf::::from(1_000_000_000u32); + let imbalance = T::Currency::issue(amount); + let value = imbalance.peek(); + }: { + Pallet::::distribute_imbalances(imbalance, Event::::BlockRewardsDistributed(value)); + } verify { - assert_last_event::(Event::::DistributionConfigurationChanged(reward_config).into()); + assert_last_event::(Event::::BlockRewardsDistributed(value).into()); } } diff --git a/pallets/block-reward/src/lib.rs b/pallets/block-reward/src/lib.rs index 2d9730a7..2a4fe7ed 100644 --- a/pallets/block-reward/src/lib.rs +++ b/pallets/block-reward/src/lib.rs @@ -47,6 +47,8 @@ use frame_support::{ use frame_system::{ensure_root, pallet_prelude::*}; use inflation_manager::{Config as InflationManagerConfig, Pallet as InflationManagerPallet}; use peaq_primitives_xcm::Balance; +use sp_runtime::{traits::AccountIdConversion, Perbill}; +use sp_std::vec::Vec; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -74,12 +76,34 @@ macro_rules! log { }; } +/// Builds a [`Sink`] targeting a Substrate pallet pot, given its `parameter_types!` +/// `PalletId` getter and a whole-percent share -- shorthand for the migration-target +/// lists a runtime otherwise has to spell out per entry. +/// +/// ```ignore +/// const BLOCK_REWARD_SINKS: [pallet_block_reward::Sink; 2] = [ +/// pallet_block_reward::block_reward_sink!(PotTreasuryId, 70), +/// pallet_block_reward::block_reward_sink!(PotStakeId, 30), +/// ]; +/// ``` +#[macro_export] +macro_rules! block_reward_sink { + ($pallet_id:ty, $percent:expr) => { + $crate::Sink { + target: $crate::RewardTarget::Pallet($crate::SinkPalletId::from_pallet_id( + <$pallet_id>::get(), + )), + share: sp_runtime::Perbill::from_percent($percent), + } + }; +} + #[frame_support::pallet] pub mod pallet { use super::*; - const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -87,11 +111,23 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// H160 -> AccountId. Bei AccountId32 + Frontier ein Adapter ueber + /// `pallet_evm::HashedAddressMapping`, bei AccountId20 + /// die Identitaet. + type AddressMapping: AddressMapping; + /// The currency trait. type Currency: Currency; - /// Used to payout rewards - type BeneficiaryPayout: BeneficiaryPayout>; + /// The sinks to adopt when the one-time `migrations::v3::MigrateToV3x` migration + /// finds the legacy, pre-Sinks fixed distribution config on chain. Decided + /// entirely by the runtime; can be removed once every live chain has migrated. + #[pallet::constant] + type MigrationSinks: Get>; + + /// Maximum number of token sinks. + #[pallet::constant] + type MaxSinks: Get; /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -100,47 +136,81 @@ pub mod pallet { type WeightInfo: WeightInfo; } + /// Current distribution config. Shares sum up to 100% always. #[pallet::storage] - #[pallet::getter(fn reward_config)] - pub(super) type RewardDistributionConfigStorage = - StorageValue<_, RewardDistributionConfig, ValueQuery>; + #[pallet::getter(fn sinks)] + pub type Sinks = StorageValue<_, SinksOf, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { /// Distribution configuration has been updated. - DistributionConfigurationChanged(RewardDistributionConfig), + TokenSinksUpdated { sinks: Vec }, /// Rewards have been distributed BlockRewardsDistributed(BalanceOf), /// Rewards have been distributed TransactionFeesDistributed(BalanceOf), + + /// `Sinks` was empty when a reward/fee distribution was attempted -- this + /// should be structurally unreachable (genesis and `set_sinks` both require a + /// valid, 100%-summing sink list), but if it ever happens the amount is + /// burned (cleanly un-minted / un-collected) rather than sent to an + /// arbitrary destination. + RewardsBurned { amount: BalanceOf }, } #[pallet::error] pub enum Error { - /// Sum of all rations must be one whole (100%) - InvalidDistributionConfiguration, + /// The sum of all shares do not equal 100%. + InvalidShareSum, + /// Two sinks have the same destination address. + DuplicateTarget, + /// One sink has a share of zero. + ZeroShare, + /// Too many sinks (exceeds maximum). + TooManySinks, } #[pallet::genesis_config] pub struct GenesisConfig { - pub reward_config: RewardDistributionConfig, - pub _phantom: PhantomData, + pub sinks: Vec, + #[serde(skip)] + pub _phantom: sp_std::marker::PhantomData, } impl Default for GenesisConfig { fn default() -> Self { - Self { reward_config: Default::default(), _phantom: PhantomData } + Self { sinks: Vec::new(), _phantom: PhantomData } } } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - assert!(self.reward_config.is_consistent()); - RewardDistributionConfigStorage::::put(self.reward_config.clone()); + // Empty stays a silent no-op: `construct_runtime!` generates an + // integrity test requiring every pallet's `GenesisConfig::default()` to + // build successfully, and there's no runtime-agnostic default `sinks` + // list that would mean anything (target `PalletId`s are runtime-specific) + // -- so `Default` has to stay `sinks: Vec::new()`, and that has to build. + // A real chain-spec that deliberately supplies a *non-empty* list still + // gets full validation: any invalid list panics below. + if self.sinks.is_empty() { + return; + } + + // Panicking here is safe and idiomatic: this runs once while building + // the chain-spec/genesis block, before the chain exists -- unlike a + // panic in `on_runtime_upgrade`, it can never halt an already-running + // chain. + let sinks = Pallet::::validate_sinks(self.sinks.clone()) + .unwrap_or_else(|e| panic!("pallet-block-reward: invalid genesis sinks: {:?}", e)); + + for sink in sinks.iter() { + frame_system::Pallet::::inc_providers(&Pallet::::resolve(&sink.target)); + } + Sinks::::put(&sinks); } } @@ -149,36 +219,58 @@ pub mod pallet { fn on_runtime_upgrade() -> frame_support::weights::Weight { migrations::on_runtime_upgrade::() } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + migrations::pre_upgrade::() + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + migrations::post_upgrade::(state) + } } #[pallet::call] impl Pallet { - /// Sets the reward distribution configuration parameters which will be used from next block - /// reward distribution. + /// Sets the list of reward sinks (and their shares) which will be used from + /// the next block reward distribution onward. /// - /// It is mandatory that all components of configuration sum up to one whole (**100%**), - /// otherwise an error `InvalidDistributionConfiguration` will be raised. + /// `new_sinks` must satisfy all of the following, otherwise the corresponding + /// error is raised: + /// - shares must sum up to exactly one whole (**100%**), or `InvalidShareSum` + /// - no individual share may be zero, or `ZeroShare` + /// - no two sinks may resolve to the same account, or `DuplicateTarget` + /// - the list must fit within `MaxSinks`, or `TooManySinks` /// - /// - `reward_distro_params` - reward distribution params + /// - `new_sinks` - the new list of reward sinks /// - /// Emits `DistributionConfigurationChanged` with config embeded into event itself. + /// Emits `TokenSinksUpdated` with the new sinks embedded into the event itself. #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::set_configuration())] - pub fn set_configuration( - origin: OriginFor, - reward_distro_params: RewardDistributionConfig, - ) -> DispatchResultWithPostInfo { + #[pallet::weight(T::WeightInfo::set_sinks(new_sinks.len() as u32))] + pub fn set_sinks(origin: OriginFor, new_sinks: SinksOf) -> DispatchResult { ensure_root(origin)?; - ensure!( - reward_distro_params.is_consistent(), - Error::::InvalidDistributionConfiguration - ); - RewardDistributionConfigStorage::::put(reward_distro_params.clone()); - - Self::deposit_event(Event::::DistributionConfigurationChanged(reward_distro_params)); - - Ok(().into()) + let new_sinks = Self::validate_sinks(new_sinks.into_inner())?; + + // Provider-Bookkeeping: neue Konten hochzaehlen, entfallene + // herunter. Damit existieren die Konten auch bei Guthaben 0 + // und `resolve_creating` kann nicht am ED scheitern. + let new_accounts: Vec = + new_sinks.iter().map(|s| Self::resolve(&s.target)).collect(); + let old_accounts: Vec = + Sinks::::get().iter().map(|s| Self::resolve(&s.target)).collect(); + + for a in new_accounts.iter().filter(|a| !old_accounts.contains(a)) { + frame_system::Pallet::::inc_providers(a); + } + for a in old_accounts.iter().filter(|a| !new_accounts.contains(a)) { + let _ = frame_system::Pallet::::dec_providers(a); + } + + Sinks::::put(&new_sinks); + Self::deposit_event(Event::TokenSinksUpdated { sinks: new_sinks.into_inner() }); + Ok(()) } } @@ -207,40 +299,89 @@ pub mod pallet { } impl Pallet { - /// Distribute any kind of imbalances between beneficiaries. + /// Validates a candidate sink list: shares must sum to exactly 100%, no share + /// may be zero, no two sinks may resolve to the same account, and the list must + /// fit within `MaxSinks`. Shared by the `set_sinks` extrinsic and by the + /// `migrations::v3::MigrateToV3x` migration. + pub(crate) fn validate_sinks(new_sinks: Vec) -> Result, Error> { + // 1. Anteile muessen exakt 100 % ergeben -- kein Rundungsrest, keine stille + // Ueberausschuettung. + let sum = new_sinks + .iter() + .try_fold(0u64, |acc, s| acc.checked_add(s.share.deconstruct() as u64)) + .ok_or(Error::::InvalidShareSum)?; + ensure!(sum == Perbill::one().deconstruct() as u64, Error::::InvalidShareSum); + ensure!(new_sinks.iter().all(|s| !s.share.is_zero()), Error::::ZeroShare); + + // 2. Auf Konto-Ebene deduplizieren, nicht auf Target-Ebene: entscheidend ist, wo das + // Geld landet. + let accounts: Vec = + new_sinks.iter().map(|s| Self::resolve(&s.target)).collect(); + for (i, a) in accounts.iter().enumerate() { + ensure!(!accounts[i + 1..].contains(a), Error::::DuplicateTarget); + } + + SinksOf::::try_from(new_sinks).map_err(|_| Error::::TooManySinks) + } + + /// Resolves to an address in dependency of the sink type / reward target. + pub fn resolve(target: &RewardTarget) -> T::AccountId { + match target { + RewardTarget::Pallet(id) => + frame_support::PalletId::from(*id).into_account_truncating(), + RewardTarget::Evm(addr) => T::AddressMapping::into_account_id(*addr), + } + } + + /// Distribute any kind of imbalances between sinks. /// /// # Arguments /// * `imbalance` - imbalance that will be split and distributed - fn distribute_imbalances(imbalance: NegativeImbalanceOf, dpt_event: Event) { - let distro_params = Self::reward_config(); - - // Pre-calculate balance which will be deposited for each beneficiary - let collator_delegator_balance = - distro_params.collators_delegators_percent * imbalance.peek(); - let coretime_balance = distro_params.coretime_percent * imbalance.peek(); - let subsidization_pool_balance = - distro_params.subsidization_pool_percent * imbalance.peek(); - let depin_staking_balance = distro_params.depin_staking_percent * imbalance.peek(); - let depin_incentivization_balance = - distro_params.depin_incentivization_percent * imbalance.peek(); - - // Prepare imbalances - let (collator_delegator_imbalance, remainder) = - imbalance.split(collator_delegator_balance); - let (coretime_imbalance, remainder) = remainder.split(coretime_balance); - let (subsidization_pool_imbalance, remainder) = - remainder.split(subsidization_pool_balance); - let (depin_staking_imbalance, remainder) = remainder.split(depin_staking_balance); - let (depin_incentivization_imbalance, treasury_imbalance) = - remainder.split(depin_incentivization_balance); - - // Payout beneficiaries - T::BeneficiaryPayout::treasury(treasury_imbalance); - T::BeneficiaryPayout::collators_delegators(collator_delegator_imbalance); - T::BeneficiaryPayout::coretime(coretime_imbalance); - T::BeneficiaryPayout::subsidization_pool(subsidization_pool_imbalance); - T::BeneficiaryPayout::depin_staking(depin_staking_imbalance); - T::BeneficiaryPayout::depin_incentivization(depin_incentivization_imbalance); + pub fn distribute_imbalances(mut credit: NegativeImbalanceOf, dpt_event: Event) { + let total = credit.peek(); + if total.is_zero() { + return; + } + + let sinks = Sinks::::get(); + + // Weight nachtragen: dieser Aufruf kommt aus einem Kontext + // (z. B. Post-Dispatch von OnChargeTransaction), der ihn nicht + // gebenchmarkt hat. + frame_system::Pallet::::register_extra_weight_unchecked( + T::WeightInfo::distribute_imbalances(sinks.len() as u32), + DispatchClass::Mandatory, + ); + + if sinks.is_empty() { + // Structurally unreachable in correct operation (see `Event::RewardsBurned`), + // but if it ever happens: dropping `credit` here safely un-mints the block + // reward / burns the collected fee via `NegativeImbalance`'s own `Drop` impl + // (which corrects `TotalIssuance` accordingly) -- no arbitrary destination + // account needed. + Self::deposit_event(Event::RewardsBurned { amount: total }); + return; + } + + let mut iter = sinks.iter().peekable(); + while let Some(sink) = iter.next() { + let part = if iter.peek().is_none() { + // Letzte Senke: alles, was uebrig ist. + sp_std::mem::replace(&mut credit, Imbalance::zero()) + } else { + let (part, rest) = credit.split(sink.share * total); + credit = rest; + part + }; + + let amount = part.peek(); + if amount.is_zero() { + continue; + } + + let who = Self::resolve(&sink.target); + T::Currency::resolve_creating(&who, part); + } Self::deposit_event(dpt_event); } diff --git a/pallets/block-reward/src/migrations.rs b/pallets/block-reward/src/migrations.rs index a249fb7e..0bf4d2db 100644 --- a/pallets/block-reward/src/migrations.rs +++ b/pallets/block-reward/src/migrations.rs @@ -2,100 +2,275 @@ use super::*; use frame_support::{storage_alias, weights::Weight}; -use sp_runtime::traits::Zero; - -// A value placed in storage that represents the current version of the block-reward storage. -// This value is used by the `on_runtime_upgrade` logic to determine whether we run storage -// migration logic. This internal storage version is independent to branch/crate versions. -#[derive( - Encode, Decode, Clone, Copy, Default, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen, -)] -pub enum ObsoleteStorageReleases { - V2_0_0, - V2_1_0, // First changes compared to releases before, renaming HardCap to MaxCurrencySupply - V2_2_0, // change the machine subsidization to parachain lease fund - #[default] - V2_3_0, // change the reward distribution configuration -} +use serde::{Deserialize, Serialize}; +use sp_runtime::Perbill; pub(crate) fn on_runtime_upgrade() -> Weight { - v2::MigrateToV2x::::on_runtime_upgrade() + v3::MigrateToV3x::::on_runtime_upgrade() } -mod v2 { - use super::*; - use sp_runtime::Perbill; +#[cfg(feature = "try-runtime")] +pub(crate) fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> +{ + v3::MigrateToV3x::::pre_upgrade() +} - #[storage_alias] - type VersionStorage = StorageValue, ObsoleteStorageReleases, ValueQuery>; +#[cfg(feature = "try-runtime")] +pub(crate) fn post_upgrade( + state: sp_std::vec::Vec, +) -> Result<(), sp_runtime::TryRuntimeError> { + v3::MigrateToV3x::::post_upgrade(state) +} - #[storage_alias] - type HardCap = StorageValue, BalanceOf, ValueQuery>; +mod v3 { + use super::*; - #[storage_alias] - type MaxCurrencySupply = StorageValue, BalanceOf, ValueQuery>; + #[derive( + PartialEq, + Eq, + Clone, + Default, + Encode, + Decode, + RuntimeDebug, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, + DecodeWithMemTracking, + )] + struct RewardDistributionConfig { + /// Base percentage of reward that goes to treasury + #[codec(compact)] + pub treasury_percent: Perbill, + /// Percentage of reward that goes to collators and delegators + #[codec(compact)] + pub collators_delegators_percent: Perbill, + /// Percentage of reward that goes to coretime + #[codec(compact)] + pub coretime_percent: Perbill, + /// Percentage of reward that goes to subsidization pool + #[codec(compact)] + pub subsidization_pool_percent: Perbill, + /// Percentage of rewards that goes to DePIN staking + #[codec(compact)] + pub depin_staking_percent: Perbill, + /// Percentage of rewards that goes to DePIN incentivization + #[codec(compact)] + pub depin_incentivization_percent: Perbill, + } #[storage_alias] - type BlockIssueReward = StorageValue, BalanceOf, ValueQuery>; + type RewardDistributionConfigStorage = + StorageValue, RewardDistributionConfig, ValueQuery>; /// Migration implementation that renames storage HardCap into MaxCurrencySupply - pub struct MigrateToV2x(sp_std::marker::PhantomData); + pub struct MigrateToV3x(sp_std::marker::PhantomData); - impl MigrateToV2x { + impl MigrateToV3x { pub fn on_runtime_upgrade() -> Weight { - let mut weight_writes = 0; let mut weight_reads = 2; + let mut weight_writes = 0; let current = Pallet::::in_code_storage_version(); let onchain_version = Pallet::::on_chain_storage_version(); if onchain_version < current { log!(info, "Enter and do the migration, {:?} < {:?}", onchain_version, current); - // Deprecated the ObsoletRelease because there are some wrong settings.. Therefore, - // try to use another checking - if HardCap::::exists() { - log!(info, "Migrating block_reward to Releases::V2_1_0"); - let storage = HardCap::::get(); - if !storage.is_zero() { - MaxCurrencySupply::::put(storage); - } - HardCap::::kill(); - log!(info, "Releases::V2_1_0 Migrating Done."); - weight_reads += 1; - weight_writes += 2 - } - - if MaxCurrencySupply::::exists() { - MaxCurrencySupply::::kill(); - } - if BlockIssueReward::::exists() { - BlockIssueReward::::kill(); - } - - log!(info, "Enter and do the migration, {:?} < {:?}", onchain_version, current); + // Deprecated storage with the old fixed distribution configuration. Its + // value no longer matters -- the runtime decides the post-migration + // sinks directly via `T::MigrationSinks` -- its mere *existence* is only + // used as the trigger for "this chain still needs migrating". if RewardDistributionConfigStorage::::exists() { - log!(info, "Migrating block_reward to Releases::V2_3_0"); - let new_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(25), - depin_staking_percent: Perbill::from_percent(5), - depin_incentivization_percent: Perbill::from_percent(15), - collators_delegators_percent: Perbill::from_percent(40), - coretime_percent: Perbill::from_percent(10), - subsidization_pool_percent: Perbill::from_percent(5), - }; - RewardDistributionConfigStorage::::put(new_config); - log!(info, "Releases::V2_3_0 Migrating Done."); - weight_reads += 1; + RewardDistributionConfigStorage::::kill(); weight_writes += 1; + + let (reads, writes) = Self::apply_sinks(T::MigrationSinks::get()); + weight_reads += reads; + weight_writes += writes; } - // Ignore the RewardDistributionConfigStorageV0 directly because it will - // automatically chain - VersionStorage::::kill(); + current.put::>(); + weight_writes += 1; + log!(info, "Migrating to {:?} Done.", current); } - T::DbWeight::get().reads_writes(weight_reads + 2, weight_writes + 2) + T::DbWeight::get().reads_writes(weight_reads, weight_writes) + } + + /// Records whether this chain actually needs migrating (`on_chain_storage_version + /// < in_code_storage_version`) pre-upgrade, so `post_upgrade` can tell a + /// migration that genuinely had nothing to do (chain already migrated) apart + /// from one that silently no-op'd -- e.g. because the legacy trigger storage + /// was unexpectedly missing, or `T::MigrationSinks` was invalid -- leaving + /// `Sinks` empty and every future block reward burned. + #[cfg(feature = "try-runtime")] + pub(crate) fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let needs_migration = + Pallet::::on_chain_storage_version() < Pallet::::in_code_storage_version(); + Ok(needs_migration.encode()) + } + + /// Asserts the storage version was bumped and, if the chain needed migrating + /// pre-upgrade, that `Sinks` ended up non-empty -- an empty `Sinks` + /// post-migration means every future block reward gets burned instead of + /// distributed (see `Pallet::distribute_imbalances`). + #[cfg(feature = "try-runtime")] + pub(crate) fn post_upgrade( + state: sp_std::vec::Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + let needed_migration: bool = Decode::decode(&mut state.as_ref()) + .map_err(|_| "block-reward migration: cannot decode pre_upgrade state")?; + + frame_support::ensure!( + Pallet::::on_chain_storage_version() >= Pallet::::in_code_storage_version(), + "block-reward migration: on-chain storage version was not bumped to the in-code version" + ); + + if needed_migration { + frame_support::ensure!( + !Sinks::::get().is_empty(), + "block-reward migration: chain needed migrating but Sinks is empty post-upgrade -- future rewards would be burned" + ); + } + + Ok(()) + } + + /// Validates `candidate` and, if valid, adopts it as the new `Sinks`. Returns + /// the number of storage reads and writes performed, for weight accounting: + /// each sink incurs one read + one write via `inc_providers` + /// (`frame_system::Account` is read then mutated), plus one final write for + /// `Sinks::put`. + /// + /// Never panics: unlike a genesis-config error (which only fails a + /// not-yet-launched chain-spec build), a panic here would halt an + /// already-running chain with real funds in it. On invalid input, `Sinks` is + /// simply left empty (drained via `FallbackTarget` instead). + fn apply_sinks(candidate: sp_std::vec::Vec) -> (u64, u64) { + match Pallet::::validate_sinks(candidate) { + Ok(sinks) => { + for sink in sinks.iter() { + frame_system::Pallet::::inc_providers(&Pallet::::resolve( + &sink.target, + )); + } + log!(info, "block-reward: migrated to {} configured sink(s)", sinks.len()); + let reads = sinks.len() as u64; + let writes = sinks.len() as u64 + 1; + Sinks::::put(&sinks); + (reads, writes) + }, + Err(e) => { + log!( + warn, + "block-reward: T::MigrationSinks is invalid ({:?}); Sinks left empty, FallbackTarget absorbs rewards until `set_sinks` is called", + e + ); + (0, 0) + }, + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + use crate::mock::*; + + #[test] + fn migration_adopts_configured_sinks_when_legacy_storage_exists() { + ExternalityBuilder::build().execute_with(|| { + // The value doesn't matter any more, only its presence as the + // "this chain still needs migrating" trigger. + RewardDistributionConfigStorage::::put( + RewardDistributionConfig::default(), + ); + + let _ = MigrateToV3x::::on_runtime_upgrade(); + + assert!(!RewardDistributionConfigStorage::::exists()); + assert_eq!( + Pallet::::on_chain_storage_version(), + Pallet::::in_code_storage_version() + ); + + let expected = ::MigrationSinks::get(); + assert!(!expected.is_empty()); + assert_eq!(Sinks::::get().into_inner(), expected); + + for sink in expected.iter() { + let account = Pallet::::resolve(&sink.target); + assert!(frame_system::Account::::get(account).providers >= 1); + } + }); + } + + #[test] + fn migration_is_noop_without_legacy_storage() { + ExternalityBuilder::build().execute_with(|| { + assert!(!RewardDistributionConfigStorage::::exists()); + + let _ = MigrateToV3x::::on_runtime_upgrade(); + + assert_eq!( + Pallet::::on_chain_storage_version(), + Pallet::::in_code_storage_version() + ); + assert!(Sinks::::get().is_empty()); + }); + } + + #[test] + #[cfg(feature = "try-runtime")] + fn post_upgrade_fails_when_migration_needed_but_sinks_ends_up_empty() { + ExternalityBuilder::build().execute_with(|| { + // Legacy trigger storage missing while the chain still reports an + // outdated storage version -- the migration silently does nothing + // beyond bumping the version, so `Sinks` stays empty and every future + // block reward would be burned. `post_upgrade` must catch this. + assert!(!RewardDistributionConfigStorage::::exists()); + + let state = MigrateToV3x::::pre_upgrade().unwrap(); + let _ = MigrateToV3x::::on_runtime_upgrade(); + + assert!(Sinks::::get().is_empty()); + assert!(MigrateToV3x::::post_upgrade(state).is_err()); + }); + } + + #[test] + #[cfg(feature = "try-runtime")] + fn post_upgrade_succeeds_when_migration_populates_sinks() { + ExternalityBuilder::build().execute_with(|| { + RewardDistributionConfigStorage::::put( + RewardDistributionConfig::default(), + ); + + let state = MigrateToV3x::::pre_upgrade().unwrap(); + let _ = MigrateToV3x::::on_runtime_upgrade(); + + assert!(!Sinks::::get().is_empty()); + assert!(MigrateToV3x::::post_upgrade(state).is_ok()); + }); + } + + #[test] + fn apply_sinks_never_panics_on_invalid_candidate() { + ExternalityBuilder::build().execute_with(|| { + // Sums to 90%, not 100% -- must degrade safely instead of panicking. + let invalid = sp_std::vec![Sink { + target: RewardTarget::Pallet(TREASURY_POT.into()), + share: Perbill::from_percent(90), + }]; + + let (reads, writes) = MigrateToV3x::::apply_sinks(invalid); + + assert_eq!(reads, 0); + assert_eq!(writes, 0); + assert!(Sinks::::get().is_empty()); + }); } } } diff --git a/pallets/block-reward/src/mock.rs b/pallets/block-reward/src/mock.rs index 3d1c3ac5..f5fe14da 100644 --- a/pallets/block-reward/src/mock.rs +++ b/pallets/block-reward/src/mock.rs @@ -1,15 +1,12 @@ -use crate::{self as pallet_block_reward, NegativeImbalanceOf}; +use crate::{self as pallet_block_reward}; -use frame_support::{ - construct_runtime, parameter_types, traits::Currency, weights::Weight, PalletId, -}; +use frame_support::{construct_runtime, parameter_types, weights::Weight, PalletId}; use frame_system::pallet_prelude::BlockNumberFor; -use sp_io::TestExternalities; - use inflation_manager::types::{InflationConfiguration, InflationParameters}; -use sp_core::{ConstU32, H256}; +use sp_core::{ConstU32, H160, H256}; +use sp_io::TestExternalities; use sp_runtime::{ - traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup}, BuildStorage, Perbill, }; @@ -109,46 +106,52 @@ impl pallet_timestamp::Config for TestRuntime { // Fake accounts used to simulate reward beneficiaries balances pub(crate) const TREASURY_POT: PalletId = PalletId(*b"moktrsry"); pub(crate) const COLLATOR_DELEGATOR_POT: PalletId = PalletId(*b"mokcolat"); -pub(crate) const CORETIME_POT: PalletId = PalletId(*b"lpreward"); -pub(crate) const SUBSIDIZATION_POT: PalletId = PalletId(*b"machiner"); -pub(crate) const DE_PINSTAKING_ACCOUNT: PalletId = PalletId(*b"destakin"); -pub(crate) const DE_PININCENTIVIZATION_ACCOUNT: PalletId = PalletId(*b"deincent"); - -// Type used as beneficiary payout handle -pub struct BeneficiaryPayout(); -impl pallet_block_reward::BeneficiaryPayout> - for BeneficiaryPayout -{ - fn treasury(reward: NegativeImbalanceOf) { - Balances::resolve_creating(&TREASURY_POT.into_account_truncating(), reward); - } - - fn collators_delegators(reward: NegativeImbalanceOf) { - Balances::resolve_creating(&COLLATOR_DELEGATOR_POT.into_account_truncating(), reward); - } - - fn coretime(reward: NegativeImbalanceOf) { - Balances::resolve_creating(&CORETIME_POT.into_account_truncating(), reward); - } - - fn subsidization_pool(reward: NegativeImbalanceOf) { - Balances::resolve_creating(&SUBSIDIZATION_POT.into_account_truncating(), reward); - } - - fn depin_staking(reward: NegativeImbalanceOf) { - Balances::resolve_creating(&DE_PINSTAKING_ACCOUNT.into_account_truncating(), reward); - } - - fn depin_incentivization(reward: NegativeImbalanceOf) { - Balances::resolve_creating( - &DE_PININCENTIVIZATION_ACCOUNT.into_account_truncating(), - reward, - ); - } -} +// Only used by the `migrations::v3::MigrateToV3x` translation test - the legacy +// distribution had six categories, so we need distinct targets for all of them. +// `into_account_truncating()` on a `u64` mock AccountId only keeps the first 4 +// bytes of the PalletId (the rest is eaten by the "modl" prefix), so these must +// differ from each other and from the two pots above in their first 4 bytes. +pub(crate) const CORETIME_POT: PalletId = PalletId(*b"coretime"); +pub(crate) const SUBSIDIZATION_POT: PalletId = PalletId(*b"subsidyp"); +pub(crate) const DEPIN_STAKING_POT: PalletId = PalletId(*b"depistak"); +pub(crate) const DEPIN_INCENTIVIZATION_POT: PalletId = PalletId(*b"dpincntv"); +pub(crate) const MACHINE_POOL_EVM: H160 = + H160(hex_literal::hex!("1111111111111111111111111111111111111111")); +pub(crate) const MACHINE_SUBSCRIPTION_LP_EVM: H160 = + H160(hex_literal::hex!("2222222222222222222222222222222222222222")); parameter_types! { pub const InfaltionPot: PalletId = PalletId(*b"inflapot"); + /// Sinks the `migrations::v3::MigrateToV3x` test adopts when it finds the + /// legacy distribution config on chain -- mirrors the six pots the real + /// runtimes used to have, just decided directly here instead of translated + /// from on-chain data. + pub MockMigrationSinks: sp_std::vec::Vec = sp_std::vec![ + pallet_block_reward::Sink { + target: pallet_block_reward::RewardTarget::Pallet(TREASURY_POT.into()), + share: Perbill::from_percent(25), + }, + pallet_block_reward::Sink { + target: pallet_block_reward::RewardTarget::Pallet(COLLATOR_DELEGATOR_POT.into()), + share: Perbill::from_percent(40), + }, + pallet_block_reward::Sink { + target: pallet_block_reward::RewardTarget::Pallet(CORETIME_POT.into()), + share: Perbill::from_percent(10), + }, + pallet_block_reward::Sink { + target: pallet_block_reward::RewardTarget::Pallet(SUBSIDIZATION_POT.into()), + share: Perbill::from_percent(5), + }, + pallet_block_reward::Sink { + target: pallet_block_reward::RewardTarget::Pallet(DEPIN_STAKING_POT.into()), + share: Perbill::from_percent(5), + }, + pallet_block_reward::Sink { + target: pallet_block_reward::RewardTarget::Pallet(DEPIN_INCENTIVIZATION_POT.into()), + share: Perbill::from_percent(15), + }, + ]; pub const DefaultTotalIssuanceNum: Balance = 10_000_000_000_000_000_000_000_000; pub const DefaultInflationConfiguration: InflationConfiguration = InflationConfiguration { inflation_parameters: InflationParameters { @@ -174,10 +177,22 @@ impl inflation_manager::Config for TestRuntime { type BlockRewardBeforeInitialize = BlockRewardBeforeInitialize; } +/// Deterministic H160 -> AccountId mapping used only for testing purposes. +pub struct MockAddressMapping; +impl crate::AddressMapping for MockAddressMapping { + fn into_account_id(address: H160) -> AccountId { + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&address.0[12..20]); + AccountId::from_be_bytes(bytes) + } +} + impl pallet_block_reward::Config for TestRuntime { - type RuntimeEvent = RuntimeEvent; + type AddressMapping = MockAddressMapping; type Currency = Balances; - type BeneficiaryPayout = BeneficiaryPayout; + type MaxSinks = ConstU32<8>; + type MigrationSinks = MockMigrationSinks; + type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_block_reward::weights::WeightInfo; } @@ -185,6 +200,14 @@ pub struct ExternalityBuilder; impl ExternalityBuilder { pub fn build() -> TestExternalities { + Self::build_with_sinks(Vec::default()) + } + + /// Like [`Self::build`], but seeds `pallet_block_reward`'s genesis config with the + /// given `sinks` -- used to exercise `GenesisConfig::build()` itself. + pub fn build_with_sinks( + sinks: sp_std::vec::Vec, + ) -> TestExternalities { let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); @@ -198,12 +221,9 @@ impl ExternalityBuilder { inflation_manager::GenesisConfig:: { _phantom: Default::default() } .assimilate_storage(&mut storage) .ok(); - pallet_block_reward::GenesisConfig:: { - reward_config: pallet_block_reward::RewardDistributionConfig::default(), - _phantom: Default::default(), - } - .assimilate_storage(&mut storage) - .ok(); + pallet_block_reward::GenesisConfig:: { sinks, _phantom: Default::default() } + .assimilate_storage(&mut storage) + .ok(); let mut ext = TestExternalities::from(storage); ext.execute_with(|| System::set_block_number(1)); diff --git a/pallets/block-reward/src/tests.rs b/pallets/block-reward/src/tests.rs index 244b81b1..ea848361 100644 --- a/pallets/block-reward/src/tests.rs +++ b/pallets/block-reward/src/tests.rs @@ -4,148 +4,197 @@ use frame_support::{ traits::{Currency, OnTimestampSet}, }; use mock::*; -use pallet_balances::NegativeImbalance; use sp_runtime::{ - traits::{BadOrigin, Zero}, + traits::{AccountIdConversion, BadOrigin, Zero}, Perbill, }; -#[test] -fn default_reward_distribution_config_is_consitent() { - let reward_config = RewardDistributionConfig::default(); - assert!(reward_config.is_consistent()); +fn treasury_sink(share: Perbill) -> Sink { + Sink { target: RewardTarget::Pallet(TREASURY_POT.into()), share } +} + +fn collator_delegator_sink(share: Perbill) -> Sink { + Sink { target: RewardTarget::Pallet(COLLATOR_DELEGATOR_POT.into()), share } +} + +fn machine_pool_sink(share: Perbill) -> Sink { + Sink { target: RewardTarget::Evm(MACHINE_POOL_EVM), share } +} + +fn machine_subscription_sink(share: Perbill) -> Sink { + Sink { target: RewardTarget::Evm(MACHINE_SUBSCRIPTION_LP_EVM), share } +} + +fn bounded(sinks: Vec) -> SinksOf { + SinksOf::::try_from(sinks).expect("test fixtures fit MaxSinks") } #[test] -fn reward_distribution_config_is_consistent() { - // 1 - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(100), - collators_delegators_percent: Zero::zero(), - coretime_percent: Zero::zero(), - subsidization_pool_percent: Zero::zero(), - depin_staking_percent: Zero::zero(), - depin_incentivization_percent: Zero::zero(), - }; - assert!(reward_config.is_consistent()); - - // 2 - let reward_config = RewardDistributionConfig { - treasury_percent: Zero::zero(), - collators_delegators_percent: Zero::zero(), - coretime_percent: Zero::zero(), - subsidization_pool_percent: Zero::zero(), - depin_staking_percent: Perbill::from_percent(50), - depin_incentivization_percent: Perbill::from_percent(50), - }; - assert!(reward_config.is_consistent()); - - // 3 - let reward_config = RewardDistributionConfig { - treasury_percent: Zero::zero(), - collators_delegators_percent: Zero::zero(), - coretime_percent: Zero::zero(), - subsidization_pool_percent: Zero::zero(), - depin_staking_percent: Zero::zero(), - depin_incentivization_percent: Zero::zero(), - }; - assert!(!reward_config.is_consistent()); - - // 4 - // 100% - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(3), - collators_delegators_percent: Perbill::from_percent(25), - coretime_percent: Perbill::from_percent(2), - subsidization_pool_percent: Perbill::from_percent(8), - depin_staking_percent: Perbill::from_percent(31), - depin_incentivization_percent: Perbill::from_percent(31), - }; - assert!(reward_config.is_consistent()); +fn is_complete_distribution_accepts_exact_100_percent() { + assert!(is_complete_distribution(&[ + treasury_sink(Perbill::from_percent(70)), + collator_delegator_sink(Perbill::from_percent(30)), + ])); } #[test] -fn reward_distribution_config_not_consistent() { - // 1 - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(100), - ..Default::default() - }; - assert!(!reward_config.is_consistent()); - - // 2 - // 99% - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(10), - collators_delegators_percent: Perbill::from_percent(33), - coretime_percent: Perbill::from_percent(2), - subsidization_pool_percent: Perbill::from_percent(14), - depin_staking_percent: Perbill::from_percent(20), - depin_incentivization_percent: Perbill::from_percent(20), - }; - assert!(!reward_config.is_consistent()); - - // 3 - // 101% - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(10), - collators_delegators_percent: Perbill::from_percent(40), - coretime_percent: Perbill::from_percent(2), - subsidization_pool_percent: Perbill::from_percent(9), - depin_staking_percent: Perbill::from_percent(20), - depin_incentivization_percent: Perbill::from_percent(20), - }; - assert!(!reward_config.is_consistent()); +fn is_complete_distribution_rejects_bad_sum_and_zero_share() { + // 90%, not 100%. + assert!(!is_complete_distribution(&[treasury_sink(Perbill::from_percent(90))])); + // Sums to 100%, but one share is zero. + assert!(!is_complete_distribution(&[ + treasury_sink(Perbill::from_percent(100)), + collator_delegator_sink(Perbill::zero()), + ])); } #[test] -pub fn set_configuration_fails() { +fn set_sinks_requires_root() { ExternalityBuilder::build().execute_with(|| { - // 1 assert_noop!( - BlockReward::set_configuration(RuntimeOrigin::signed(1), Default::default()), + BlockReward::set_sinks( + RuntimeOrigin::signed(1), + bounded(vec![treasury_sink(Perbill::one())]) + ), BadOrigin ); + }) +} - // 2 - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(100), - ..Default::default() - }; - assert!(!reward_config.is_consistent()); +#[test] +fn set_sinks_fails_on_bad_share_sum() { + ExternalityBuilder::build().execute_with(|| { + // Sum too low (90%) + assert_noop!( + BlockReward::set_sinks( + RuntimeOrigin::root(), + bounded(vec![ + treasury_sink(Perbill::from_percent(60)), + collator_delegator_sink(Perbill::from_percent(30)), + ]) + ), + Error::::InvalidShareSum + ); + + // Sum too high (110%, saturates individually but doesn't sum to 100%) assert_noop!( - BlockReward::set_configuration(RuntimeOrigin::root(), reward_config), - Error::::InvalidDistributionConfiguration, + BlockReward::set_sinks( + RuntimeOrigin::root(), + bounded(vec![ + treasury_sink(Perbill::from_percent(60)), + collator_delegator_sink(Perbill::from_percent(50)), + ]) + ), + Error::::InvalidShareSum ); }) } #[test] -pub fn set_configuration_is_ok() { +fn set_sinks_fails_on_zero_share() { ExternalityBuilder::build().execute_with(|| { - // custom config so it differs from the default one - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(3), - collators_delegators_percent: Perbill::from_percent(60), - coretime_percent: Perbill::from_percent(2), - subsidization_pool_percent: Perbill::from_percent(7), - depin_staking_percent: Perbill::from_percent(14), - depin_incentivization_percent: Perbill::from_percent(14), - }; - assert!(reward_config.is_consistent()); - - assert_ok!(BlockReward::set_configuration(RuntimeOrigin::root(), reward_config.clone())); - System::assert_last_event(mock::RuntimeEvent::BlockReward( - Event::DistributionConfigurationChanged(reward_config.clone()), - )); + assert_noop!( + BlockReward::set_sinks( + RuntimeOrigin::root(), + bounded(vec![ + treasury_sink(Perbill::one()), + collator_delegator_sink(Perbill::zero()), + ]) + ), + Error::::ZeroShare + ); + }) +} + +#[test] +fn set_sinks_fails_on_duplicate_target() { + ExternalityBuilder::build().execute_with(|| { + assert_noop!( + BlockReward::set_sinks( + RuntimeOrigin::root(), + bounded(vec![ + treasury_sink(Perbill::from_percent(50)), + treasury_sink(Perbill::from_percent(50)), + ]) + ), + Error::::DuplicateTarget + ); + }) +} + +#[test] +fn set_sinks_is_ok() { + ExternalityBuilder::build().execute_with(|| { + let sinks = vec![ + treasury_sink(Perbill::from_percent(30)), + collator_delegator_sink(Perbill::from_percent(40)), + machine_pool_sink(Perbill::from_percent(30)), + ]; + assert_ok!(BlockReward::set_sinks(RuntimeOrigin::root(), bounded(sinks.clone()))); + + System::assert_last_event(mock::RuntimeEvent::BlockReward(Event::TokenSinksUpdated { + sinks: sinks.clone(), + })); + + assert_eq!(BlockReward::sinks().into_inner(), sinks); + }) +} + +#[test] +fn genesis_build_populates_sinks() { + let sinks = vec![ + treasury_sink(Perbill::from_percent(60)), + collator_delegator_sink(Perbill::from_percent(40)), + ]; + ExternalityBuilder::build_with_sinks(sinks.clone()).execute_with(|| { + assert_eq!(BlockReward::sinks().into_inner(), sinks.clone()); + + for sink in &sinks { + let account = BlockReward::resolve(&sink.target); + assert!(frame_system::Account::::get(account).providers >= 1); + } + }); +} + +#[test] +#[should_panic(expected = "invalid genesis sinks")] +fn genesis_build_panics_on_invalid_sinks() { + // Sums to 90%, not 100% -- must panic at chain-spec build time rather than + // silently launching a chain with a broken distribution. + ExternalityBuilder::build_with_sinks(vec![treasury_sink(Perbill::from_percent(90))]); +} + +#[test] +fn resolve_maps_pallet_and_evm_targets() { + ExternalityBuilder::build().execute_with(|| { + let expected_treasury: AccountId = >::into_account_truncating(&TREASURY_POT); + assert_eq!( + BlockReward::resolve(&RewardTarget::Pallet(TREASURY_POT.into())), + expected_treasury + ); - assert_eq!(RewardDistributionConfigStorage::::get(), reward_config); + let expected_machine_pool = + >::into_account_id(MACHINE_POOL_EVM); + assert_eq!( + BlockReward::resolve(&RewardTarget::Evm(MACHINE_POOL_EVM)), + expected_machine_pool + ); }) } #[test] pub fn inflation_and_total_issuance_as_expected() { ExternalityBuilder::build().execute_with(|| { + // Needs a configured sink, otherwise the issued reward is burned right back + // (see `distribute_imbalances_burns_reward_if_sinks_somehow_empty`) and + // issuance wouldn't grow at all -- this test is about inflation, not sinks. + assert_ok!(BlockReward::set_sinks( + RuntimeOrigin::root(), + bounded(vec![treasury_sink(Perbill::one())]) + )); + let init_issuance = ::Currency::total_issuance(); let block_reward: Balance = InflationManagerPallet::::block_rewards(); @@ -164,74 +213,84 @@ pub fn inflation_and_total_issuance_as_expected() { } #[test] -pub fn reward_distribution_as_expected() { +pub fn distribute_imbalances_burns_reward_if_sinks_somehow_empty() { ExternalityBuilder::build().execute_with(|| { - // Ensure that initially, all beneficiaries have no free balance - let init_balance_snapshot = FreeBalanceSnapshot::new(); - assert!(init_balance_snapshot.is_zero()); - - // Prepare a custom config (easily discernable percentages for visual verification) - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(10), - collators_delegators_percent: Perbill::from_percent(40), - coretime_percent: Perbill::from_percent(2), - subsidization_pool_percent: Perbill::from_percent(8), - depin_staking_percent: Perbill::from_percent(20), - depin_incentivization_percent: Perbill::from_percent(20), - }; - assert!(reward_config.is_consistent()); - assert_ok!(BlockReward::set_configuration(RuntimeOrigin::root(), reward_config.clone())); - - // Issue rewards a couple of times and verify distribution is as expected - for _block in 1..=100 { - let init_balance_state = FreeBalanceSnapshot::new(); - let rewards = Rewards::calculate(&reward_config); + // `Sinks` can never actually become empty through any pallet-supported path + // -- genesis and `set_sinks` both require a valid, 100%-summing list. This + // forces the otherwise-unreachable state directly via storage, to prove the + // defensive branch degrades safely (burns the reward) instead of losing or + // misdirecting funds. + Sinks::::kill(); + assert!(BlockReward::sinks().is_empty()); + + let issuance_before = ::Currency::total_issuance(); + let block_reward: Balance = InflationManagerPallet::::block_rewards(); - BlockReward::on_timestamp_set(0); + BlockReward::on_timestamp_set(0); - let final_balance_state = FreeBalanceSnapshot::new(); - init_balance_state.assert_distribution(&final_balance_state, &rewards); - } + // Issued, then immediately burned again -- net effect on issuance: none. + assert_eq!(::Currency::total_issuance(), issuance_before); + System::assert_has_event(mock::RuntimeEvent::BlockReward(Event::RewardsBurned { + amount: block_reward, + })); }) } #[test] -pub fn reward_distribution_no_adjustable_part() { +pub fn reward_distribution_as_expected() { ExternalityBuilder::build().execute_with(|| { - let reward_config = RewardDistributionConfig { - treasury_percent: Perbill::from_percent(10), - collators_delegators_percent: Perbill::from_percent(3), - coretime_percent: Perbill::from_percent(2), - subsidization_pool_percent: Perbill::from_percent(10), - depin_staking_percent: Perbill::from_percent(50), - depin_incentivization_percent: Perbill::from_percent(25), - }; - assert!(reward_config.is_consistent()); - assert_ok!(BlockReward::set_configuration(RuntimeOrigin::root(), reward_config.clone())); - - // no adjustable part so we don't expect rewards to change with TVL percentage - let const_rewards = Rewards::calculate(&reward_config); + let sinks = vec![ + treasury_sink(Perbill::from_percent(10)), + collator_delegator_sink(Perbill::from_percent(40)), + machine_pool_sink(Perbill::from_percent(21)), + machine_subscription_sink(Perbill::from_percent(29)), + ]; + assert_ok!(BlockReward::set_sinks(RuntimeOrigin::root(), bounded(sinks.clone()))); - for _block in 1..=100 { - let init_balance_state = FreeBalanceSnapshot::new(); - let rewards = Rewards::calculate(&reward_config); + let accounts: Vec = + sinks.iter().map(|s| BlockReward::resolve(&s.target)).collect(); + + // Ensure that initially, all sinks have no free balance + let init_balances = free_balances(&accounts); + assert!(init_balances.iter().all(|b| b.is_zero())); - assert_eq!(rewards, const_rewards); + for _block in 1..=100 { + let before = free_balances(&accounts); + let total: Balance = InflationManagerPallet::::block_rewards(); + let expected = expected_shares(total, &sinks); BlockReward::on_timestamp_set(0); - let final_balance_state = FreeBalanceSnapshot::new(); - init_balance_state.assert_distribution(&final_balance_state, &rewards); + let after = free_balances(&accounts); + for i in 0..accounts.len() { + assert_eq!(after[i], before[i] + expected[i]); + } } }) } #[test] -pub fn on_unbalanced() { +pub fn on_unbalanced_distributes_according_to_sinks() { ExternalityBuilder::build().execute_with(|| { + let sinks = vec![ + treasury_sink(Perbill::from_percent(70)), + machine_pool_sink(Perbill::from_percent(30)), + ]; + assert_ok!(BlockReward::set_sinks(RuntimeOrigin::root(), bounded(sinks.clone()))); + + let accounts: Vec = + sinks.iter().map(|s| BlockReward::resolve(&s.target)).collect(); + let before = free_balances(&accounts); + let amount = 1_000_000_000_000 as Balance; let imbalance = ::Currency::issue(amount); BlockReward::on_unbalanced(imbalance); + + let after = free_balances(&accounts); + let expected = expected_shares(amount, &sinks); + for i in 0..accounts.len() { + assert_eq!(after[i], before[i] + expected[i]); + } }) } @@ -248,146 +307,18 @@ pub fn on_unbalanceds() { }) } -/// Represents free balance snapshot at a specific point in time -#[derive(PartialEq, Eq, Clone, RuntimeDebug)] -struct FreeBalanceSnapshot { - treasury: Balance, - collators_delegators: Balance, - coretime: Balance, - subsidization_pool: Balance, - depin_staking: Balance, - depin_incentivization: Balance, +/// Reads the free balance of every given account, preserving order. +fn free_balances(accounts: &[AccountId]) -> Vec { + accounts.iter().map(Balances::free_balance).collect() } -impl FreeBalanceSnapshot { - /// Creates a new free balance snapshot using current balance state. - /// - /// Future balance changes won't be reflected in this instance. - fn new() -> Self { - Self { - treasury: - ::Currency::free_balance( - >::into_account_truncating(&TREASURY_POT), - ), - collators_delegators: - ::Currency::free_balance( - >::into_account_truncating(&COLLATOR_DELEGATOR_POT), - ), - coretime: - ::Currency::free_balance( - >::into_account_truncating(&CORETIME_POT), - ), - subsidization_pool: - ::Currency::free_balance( - >::into_account_truncating(&SUBSIDIZATION_POT), - ), - depin_staking: - ::Currency::free_balance( - >::into_account_truncating(&DE_PINSTAKING_ACCOUNT), - ), - depin_incentivization: - ::Currency::free_balance( - >::into_account_truncating(&DE_PININCENTIVIZATION_ACCOUNT), - ), - } - } - - /// `true` if all free balances equal `Zero`, `false` otherwise - fn is_zero(&self) -> bool { - self.treasury.is_zero() && - self.collators_delegators.is_zero() && - self.coretime.is_zero() && - self.subsidization_pool.is_zero() && - self.depin_staking.is_zero() && - self.depin_incentivization.is_zero() - } - - /// Asserts that `post_reward_state` is as expected. - /// - /// Increase in balances, based on `rewards` values, is verified. - fn assert_distribution(&self, post_reward_state: &Self, rewards: &Rewards) { - println!("pre: {:?}", self); - println!("post_reward_state: {:?}", post_reward_state); - println!("rewards: {:?}", rewards); - - assert_eq!(self.treasury + rewards.treasury_reward, post_reward_state.treasury); - assert_eq!( - self.collators_delegators + rewards.collators_delegators_reward, - post_reward_state.collators_delegators - ); - assert_eq!(self.coretime + rewards.coretime_reward, post_reward_state.coretime); - assert_eq!( - self.subsidization_pool + rewards.subsidization_pool_reward, - post_reward_state.subsidization_pool - ); - assert_eq!( - self.depin_staking + rewards.depin_staking_reward, - post_reward_state.depin_staking - ); - assert_eq!( - self.depin_incentivization + rewards.depin_incentivization_reward, - post_reward_state.depin_incentivization - ); - } -} - -/// Represents reward distribution balances for a single distribution. -#[derive(PartialEq, Eq, Clone, RuntimeDebug)] -struct Rewards { - treasury_reward: Balance, - collators_delegators_reward: Balance, - coretime_reward: Balance, - subsidization_pool_reward: Balance, - depin_staking_reward: Balance, - depin_incentivization_reward: Balance, -} - -impl Rewards { - /// Pre-calculates the reward distribution, using the provided `RewardDistributionConfig`. - /// Method assumes that total issuance will be increased by `BLOCK_REWARD`. - fn calculate(reward_config: &RewardDistributionConfig) -> Self { - let block_reward: Balance = InflationManager::block_rewards(); - - let imbalance = NegativeImbalance::::new(block_reward); - - let collators_delegators_reward_imbalance = - reward_config.collators_delegators_percent * imbalance.peek(); - let coretime_reward_imbalance = reward_config.coretime_percent * imbalance.peek(); - let subsidization_pool_reward_imbalance = - reward_config.subsidization_pool_percent * imbalance.peek(); - let depin_staking_reward_imbalance = reward_config.depin_staking_percent * imbalance.peek(); - let depin_incentivization_reward_imbalance = - reward_config.depin_incentivization_percent * imbalance.peek(); - - // Prepare imbalances - let (collator_delegator_reward, remainder) = - imbalance.split(collators_delegators_reward_imbalance); - let (coretime_reward, remainder) = remainder.split(coretime_reward_imbalance); - let (subsidization_pool_reward, remainder) = - remainder.split(subsidization_pool_reward_imbalance); - let (depin_staking_reward, remainder) = remainder.split(depin_staking_reward_imbalance); - let (depin_incentivization_reward, treasury_reward) = - remainder.split(depin_incentivization_reward_imbalance); - - Self { - treasury_reward: treasury_reward.peek(), - collators_delegators_reward: collator_delegator_reward.peek(), - coretime_reward: coretime_reward.peek(), - subsidization_pool_reward: subsidization_pool_reward.peek(), - depin_staking_reward: depin_staking_reward.peek(), - depin_incentivization_reward: depin_incentivization_reward.peek(), - } +/// Mirrors the pallet's split logic: every sink but the last gets `share * total` +/// (rounded down), the last sink absorbs the remainder so nothing is lost to rounding. +fn expected_shares(total: Balance, sinks: &[Sink]) -> Vec { + let mut shares: Vec = sinks.iter().map(|s| s.share * total).collect(); + if let Some(last) = shares.len().checked_sub(1) { + let sum_of_rest: Balance = shares[..last].iter().sum(); + shares[last] = total - sum_of_rest; } + shares } diff --git a/pallets/block-reward/src/types.rs b/pallets/block-reward/src/types.rs index 08a17f5a..3ee5d483 100644 --- a/pallets/block-reward/src/types.rs +++ b/pallets/block-reward/src/types.rs @@ -1,11 +1,9 @@ //! Type and trait definitions of the crate -use frame_support::{pallet_prelude::*, traits::Currency}; -use sp_runtime::{traits::CheckedAdd, Perbill}; -use sp_std::vec; +use frame_support::{pallet_prelude::*, traits::Currency, Deserialize, Serialize}; +use sp_runtime::Perbill; use crate::pallet::Config as PalletConfig; -use serde::{Deserialize, Serialize}; /// The balance type of this pallet. pub(crate) type BalanceOf = @@ -16,155 +14,124 @@ pub(crate) type NegativeImbalanceOf = <::Currency as Curre ::AccountId, >>::NegativeImbalance; -/// Defines functions used to payout the beneficiaries of block rewards -pub trait BeneficiaryPayout { - /// Payout reward to the treasury - fn treasury(reward: Imbalance); - - /// Payout reward to the collators - fn collators_delegators(reward: Imbalance); - - /// Payout LP users - fn coretime(reward: Imbalance); - - /// Payout Machines - fn subsidization_pool(reward: Imbalance); - - /// Payout DePIN staking rewards - fn depin_staking(reward: Imbalance); - - /// Payout DePIN incentivization - fn depin_incentivization(reward: Imbalance); -} - -/// After next next version, we can remove this RewardDistributionConfigV0 -/// List of configuration parameters used to calculate reward distribution portions for all the -/// beneficiaries. +/// Encoding-identical to `PalletId` (both are simply `[u8; 8]`), +/// just with the derives needed for `BoundedVec` storage. #[derive( - PartialEq, - Eq, Clone, + Copy, + Eq, + PartialEq, Encode, Decode, - RuntimeDebug, - TypeInfo, + DecodeWithMemTracking, + Deserialize, MaxEncodedLen, + RuntimeDebug, Serialize, - Deserialize, + TypeInfo, )] -pub struct RewardDistributionConfigV0 { - /// Base percentage of reward that goes to treasury - #[codec(compact)] - pub treasury_percent: Perbill, - /// Percentage of rewards that goes to dApps - #[codec(compact)] - pub dapps_percent: Perbill, - /// Percentage of reward that goes to collators - #[codec(compact)] - pub collators_percent: Perbill, - /// Percentage of reward that goes to lp users - #[codec(compact)] - pub lp_percent: Perbill, - /// Percentage of reward that goes to machines - #[codec(compact)] - pub machines_percent: Perbill, - /// Percentage of reward that goes to machines subsidization - #[codec(compact)] - pub machines_subsidization_percent: Perbill, +pub struct SinkPalletId(pub [u8; 8]); + +impl SinkPalletId { + /// `const fn` equivalent of `From`, usable to build `Sink` arrays as + /// compile-time constants (trait methods like `Into::into` can't be `const` on + /// stable Rust). + pub const fn from_pallet_id(id: frame_support::PalletId) -> Self { + Self(id.0) + } } -impl Default for RewardDistributionConfigV0 { - /// `default` values based on configuration at the time of writing this code. - /// Should be overridden by desired params. - fn default() -> Self { - RewardDistributionConfigV0 { - treasury_percent: Perbill::from_percent(15), - dapps_percent: Perbill::from_percent(45), - collators_percent: Perbill::from_percent(10), - lp_percent: Perbill::from_percent(20), - machines_percent: Perbill::from_percent(5), - machines_subsidization_percent: Perbill::from_percent(5), - } +impl From for frame_support::PalletId { + fn from(v: SinkPalletId) -> Self { + frame_support::PalletId(v.0) + } +} +impl From for SinkPalletId { + fn from(v: frame_support::PalletId) -> Self { + Self(v.0) } } -/// List of configuration parameters used to calculate reward distribution portions for all the -/// beneficiaries. +/// A single reward target type. Can be either a pallet, or an EVM address. #[derive( - PartialEq, - Eq, Clone, + Copy, + Eq, + PartialEq, Encode, Decode, + DecodeWithMemTracking, + Deserialize, + MaxEncodedLen, RuntimeDebug, + Serialize, TypeInfo, +)] +pub enum RewardTarget { + /// A Substrate-based pot, derived by using PalletId + Pallet(SinkPalletId), + /// A H160 address of an EVM smart contract + Evm(sp_core::H160), +} + +/// One token sink with its share of the total distribution. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Encode, + Decode, + DecodeWithMemTracking, + Deserialize, MaxEncodedLen, + RuntimeDebug, Serialize, - Deserialize, - DecodeWithMemTracking, + TypeInfo, )] -pub struct RewardDistributionConfig { - /// Base percentage of reward that goes to treasury - #[codec(compact)] - pub treasury_percent: Perbill, - /// Percentage of reward that goes to collators and delegators - #[codec(compact)] - pub collators_delegators_percent: Perbill, - /// Percentage of reward that goes to coretime - #[codec(compact)] - pub coretime_percent: Perbill, - /// Percentage of reward that goes to subsidization pool - #[codec(compact)] - pub subsidization_pool_percent: Perbill, - /// Percentage of rewards that goes to DePIN staking - #[codec(compact)] - pub depin_staking_percent: Perbill, - /// Percentage of rewards that goes to DePIN incentivization - #[codec(compact)] - pub depin_incentivization_percent: Perbill, +pub struct Sink { + /// The official target, pallet or an EVM contract. + pub target: RewardTarget, + /// The percentage of token shares. + pub share: Perbill, } -impl Default for RewardDistributionConfig { - /// `default` values based on configuration at the time of writing this code. - /// Should be overridden by desired params. - fn default() -> Self { - RewardDistributionConfig { - treasury_percent: Perbill::from_percent(25), - collators_delegators_percent: Perbill::from_percent(40), - coretime_percent: Perbill::from_percent(10), - subsidization_pool_percent: Perbill::from_percent(5), - depin_staking_percent: Perbill::from_percent(5), - depin_incentivization_percent: Perbill::from_percent(15), +/// Helper definition to get the maximum number of sinks for this pallet. +pub type SinksOf = BoundedVec::MaxSinks>; + +/// Compile-time check that a fixed sink list's shares sum to exactly 100% and that +/// no individual share is zero. Meant to be used from a runtime crate defining its +/// `MigrationSinks`/genesis sinks as a `const` array, e.g.: +/// +/// ```ignore +/// const BLOCK_REWARD_SINKS: [Sink; 2] = [ ... ]; +/// const _: () = assert!(pallet_block_reward::is_complete_distribution(&BLOCK_REWARD_SINKS)); +/// ``` +/// +/// This can only check what's knowable without a `Config` (no `AddressMapping`, no +/// `MaxSinks`), so it does *not* replace the pallet's own runtime `validate_sinks` +/// (which additionally rejects duplicate-target and over-`MaxSinks` sink lists) -- +/// it just catches the single most common mistake, a bad percentage split, at +/// `cargo build`/CI time instead of silently degrading a live chain's distribution +/// during a runtime upgrade. +pub const fn is_complete_distribution(sinks: &[Sink]) -> bool { + let mut sum: u64 = 0; + let mut i = 0; + while i < sinks.len() { + let share = sinks[i].share.deconstruct(); + if share == 0 { + return false; } + sum += share as u64; + i += 1; } + // 1_000_000_000 == Perbill::ACCURACY (its `PerThing::ACCURACY`); spelled out as a + // literal because pulling in the `PerThing` trait just for this const isn't worth it. + sum == 1_000_000_000 } -impl RewardDistributionConfig { - /// `true` if sum of all percentages is `one whole`, `false` otherwise. - pub fn is_consistent(&self) -> bool { - // TODO: perhaps this can be written in a more cleaner way? - // experimental-only `try_reduce` could be used but it's not available - // https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_reduce - - let variables = vec![ - &self.treasury_percent, - &self.collators_delegators_percent, - &self.coretime_percent, - &self.subsidization_pool_percent, - &self.depin_staking_percent, - &self.depin_incentivization_percent, - ]; - - let mut accumulator = Perbill::zero(); - for config_param in variables { - let result = accumulator.checked_add(config_param); - if let Some(mid_result) = result { - accumulator = mid_result; - } else { - return false; - } - } - - Perbill::one() == accumulator - } +/// Minimal trait for mapping addresses from H160 into SS58. +pub trait AddressMapping { + /// The mapping function. + fn into_account_id(address: sp_core::H160) -> AccountId; } diff --git a/pallets/block-reward/src/weightinfo.rs b/pallets/block-reward/src/weightinfo.rs index b26b7ba5..ccb42b89 100644 --- a/pallets/block-reward/src/weightinfo.rs +++ b/pallets/block-reward/src/weightinfo.rs @@ -3,5 +3,6 @@ use frame_support::weights::Weight; pub trait WeightInfo { - fn set_configuration() -> Weight; + fn set_sinks(n: u32) -> Weight; + fn distribute_imbalances(n: u32) -> Weight; } diff --git a/pallets/block-reward/src/weights.rs b/pallets/block-reward/src/weights.rs index be3d2b04..3c240ba5 100644 --- a/pallets/block-reward/src/weights.rs +++ b/pallets/block-reward/src/weights.rs @@ -1,10 +1,10 @@ //! Autogenerated weights for `pallet_block_reward` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-09-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 47.0.0 +//! DATE: 2026-08-26, STEPS: `50`, REPEAT: `50`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ip-99-1-200-206`, CPU: `AMD EPYC 7R32` +//! HOSTNAME: `neutrinoks`, CPU: `13th Gen Intel(R) Core(TM) i7-13700H` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev-local")`, DB CACHE: 1024 // Executed Command: @@ -12,13 +12,12 @@ // benchmark // pallet // --chain=dev-local -// --execution=native // --wasm-execution=compiled // --pallet=pallet_block_reward // --extrinsic=* // --steps=50 -// --repeat=20 -// --output=weight.rs +// --repeat=50 +// --output=pallets/block-reward/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -31,15 +30,45 @@ use core::marker::PhantomData; /// Weight functions for `pallet_block_reward`. pub struct WeightInfo(PhantomData); impl crate::WeightInfo for WeightInfo { - /// Storage: `BlockReward::RewardDistributionConfigStorage` (r:0 w:1) - /// Proof: `BlockReward::RewardDistributionConfigStorage` (`max_values`: Some(1), `max_size`: Some(24), added: 519, mode: `MaxEncodedLen`) - fn set_configuration() -> Weight { + /// Storage: `BlockReward::Sinks` (r:1 w:1) + /// Proof: `BlockReward::Sinks` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:10 w:10) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AddressUnification::EvmAddresses` (r:2 w:0) + /// Proof: `AddressUnification::EvmAddresses` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 8]`. + fn set_sinks(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 9_320_000 picoseconds. - Weight::from_parts(9_620_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `440 + n * (121 ±0)` + // Estimated: `6196 + n * (2603 ±0)` + // Minimum execution time: 28_075_000 picoseconds. + Weight::from_parts(27_197_055, 0) + .saturating_add(Weight::from_parts(0, 6196)) + // Standard Error: 17_112 + .saturating_add(Weight::from_parts(3_691_316, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(n.into())) + } + /// Storage: `BlockReward::Sinks` (r:1 w:0) + /// Proof: `BlockReward::Sinks` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:8 w:8) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 8]`. + fn distribute_imbalances(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `59 + n * (165 ±0)` + // Estimated: `1686 + n * (2603 ±0)` + // Minimum execution time: 17_292_000 picoseconds. + Weight::from_parts(6_598_074, 0) + .saturating_add(Weight::from_parts(0, 1686)) + // Standard Error: 10_190 + .saturating_add(Weight::from_parts(11_656_534, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(n.into())) } } diff --git a/pallets/parachain-staking/src/lib.rs b/pallets/parachain-staking/src/lib.rs index c3363bf4..3781e9c5 100644 --- a/pallets/parachain-staking/src/lib.rs +++ b/pallets/parachain-staking/src/lib.rs @@ -1971,7 +1971,7 @@ pub mod pallet { let collator = ensure_signed(origin)?; CandidatePool::::get(&collator).ok_or(Error::::CandidateNotFound)?; if commission > Permill::from_percent(100) { - return Err(Error::::CommissionTooHigh.into()) + return Err(Error::::CommissionTooHigh.into()); } >::mutate(&collator, |maybe_candidate| { @@ -2803,7 +2803,7 @@ pub mod pallet { fn payout_collator() { // if there's no previous round, i.e, genesis round, then skip if Self::round().current.is_zero() { - return + return; } if let Some(payout_info) = DelayedPayoutInfo::::get() { diff --git a/pallets/xc-asset-config/src/tests.rs b/pallets/xc-asset-config/src/tests.rs index 943476d5..d62eb038 100644 --- a/pallets/xc-asset-config/src/tests.rs +++ b/pallets/xc-asset-config/src/tests.rs @@ -366,4 +366,3 @@ fn different_xcm_versions_are_ok() { assert_eq!(XcAssetConfig::get_xc_asset_location(asset_id), Some(new_asset_location)); }) } - diff --git a/runtime/krest/src/lib.rs b/runtime/krest/src/lib.rs index b008eac3..ab800a66 100644 --- a/runtime/krest/src/lib.rs +++ b/runtime/krest/src/lib.rs @@ -5,6 +5,8 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +extern crate alloc; + // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); @@ -897,72 +899,53 @@ impl parachain_staking::Config for Runtime { type WeightInfo = parachain_staking::weights::WeightInfo; } -/// Implements the adapters for depositing unbalanced tokens on pots -/// of various pallets, e.g. Peaq-MOR, Peaq-Treasury etc. -macro_rules! impl_to_pot_adapter { - ($name:ident, $pot:ident, $negbal:ident) => { - pub struct $name; - impl OnUnbalanced<$negbal> for $name { - fn on_unbalanced(amount: $negbal) { - Self::on_nonzero_unbalanced(amount); - } +/// Sinks adopted by `migrations::v3::MigrateToV3x` when it finds the legacy, +/// pre-Sinks fixed distribution config on chain, and the default genesis sinks for +/// fresh `--chain krest-local` networks (see `node/src/parachain/krest_chain_spec.rs`). +/// Krest keeps the exact same six pots and shares the legacy config used to have. +/// +/// A `const` array (not a `parameter_types!` `Vec` literal) so the 100%-sum +/// invariant can be, and is, checked at compile time below. +const BLOCK_REWARD_SINKS: [pallet_block_reward::Sink; 6] = [ + pallet_block_reward::block_reward_sink!(PotTreasuryId, 25), + pallet_block_reward::block_reward_sink!(PotStakeId, 40), + pallet_block_reward::block_reward_sink!(PotCoretimeId, 10), + pallet_block_reward::block_reward_sink!(PotSubsidizationId, 5), + pallet_block_reward::block_reward_sink!(PotDepinStakingId, 5), + pallet_block_reward::block_reward_sink!(PotDepinIncentivisationId, 15), +]; +const _: () = assert!( + pallet_block_reward::is_complete_distribution(&BLOCK_REWARD_SINKS), + "BLOCK_REWARD_SINKS must sum to exactly 100% with no zero shares" +); - fn on_nonzero_unbalanced(amount: $negbal) { - let pot = $pot::get().into_account_truncating(); - Balances::resolve_creating(&pot, amount); - } - } - }; -} +parameter_types! { + /// Upper bound on the number of configurable block-reward token sinks. + pub const MaxBlockRewardSinks: u32 = 8; -impl_to_pot_adapter!(ToStakingPot, PotStakeId, NegativeImbalance); -impl_to_pot_adapter!(ToCoreTimePot, PotCoretimeId, NegativeImbalance); -impl_to_pot_adapter!(ToSubsidizationPot, PotSubsidizationId, NegativeImbalance); -impl_to_pot_adapter!(ToDepinStakingPot, PotDepinStakingId, NegativeImbalance); -impl_to_pot_adapter!(ToDepinIncentivizationPot, PotDepinIncentivisationId, NegativeImbalance); + pub BlockRewardMigrationSinks: sp_std::vec::Vec = BLOCK_REWARD_SINKS.to_vec(); +} -pub struct ToTreasuryPot; -impl OnUnbalanced for ToTreasuryPot { - fn on_nonzero_unbalanced(amount: NegativeImbalance) { - let pot = PotTreasuryId::get().into_account_truncating(); - Balances::resolve_creating(&pot, amount); +/// Maps an EVM address to a Substrate `AccountId`, reusing the same hashing scheme +/// already used to unify EVM and native accounts elsewhere in this runtime. +pub struct BlockRewardAddressMapping; +impl pallet_block_reward::AddressMapping for BlockRewardAddressMapping { + fn into_account_id(address: H160) -> AccountId { + as pallet_evm::AddressMapping>::into_account_id( + address, + ) } } impl pallet_block_reward::Config for Runtime { + type AddressMapping = BlockRewardAddressMapping; type Currency = Balances; - type BeneficiaryPayout = BeneficiaryPayout; + type MaxSinks = MaxBlockRewardSinks; + type MigrationSinks = BlockRewardMigrationSinks; type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_block_reward::weights::WeightInfo; } -pub struct BeneficiaryPayout(); -impl pallet_block_reward::BeneficiaryPayout for BeneficiaryPayout { - fn treasury(reward: NegativeImbalance) { - ToTreasuryPot::on_unbalanced(reward); - } - - fn collators_delegators(reward: NegativeImbalance) { - ToStakingPot::on_unbalanced(reward); - } - - fn coretime(reward: NegativeImbalance) { - ToCoreTimePot::on_unbalanced(reward); - } - - fn subsidization_pool(reward: NegativeImbalance) { - ToSubsidizationPot::on_unbalanced(reward); - } - - fn depin_staking(reward: NegativeImbalance) { - ToDepinStakingPot::on_unbalanced(reward); - } - - fn depin_incentivization(reward: NegativeImbalance) { - ToDepinIncentivizationPot::on_unbalanced(reward); - } -} - parameter_types! { pub const GetNativeAssetId: StorageAssetId = NATIVE_ASSET_ID; } @@ -2209,7 +2192,7 @@ impl_runtime_apis! { fn dispatch_benchmark( config: frame_benchmarking::BenchmarkConfig - ) -> Result, sp_runtime::RuntimeString> { + ) -> Result, alloc::string::String> { use frame_benchmarking::{baseline, BenchmarkBatch}; use frame_support::traits::TrackedStorageKey; use frame_system_benchmarking::Pallet as SystemBench; diff --git a/runtime/peaq-dev/src/lib.rs b/runtime/peaq-dev/src/lib.rs index 18450734..fd7208dd 100644 --- a/runtime/peaq-dev/src/lib.rs +++ b/runtime/peaq-dev/src/lib.rs @@ -2,6 +2,8 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +extern crate alloc; + // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); @@ -108,7 +110,6 @@ use peaq_rpc_primitives_txpool::TxPoolResponse; use zenlink_protocol::AssetId as ZenlinkAssetId; pub use peaq_pallet_did; -use peaq_pallet_mor::mor::MorBalance; pub use peaq_pallet_mor::{self, types::MorConfig}; pub use peaq_pallet_rbac; pub use peaq_pallet_storage; @@ -184,7 +185,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 108, + spec_version: 109, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -899,74 +900,51 @@ impl parachain_staking::Config for Runtime { type WeightInfo = parachain_staking::weights::WeightInfo; } -/// Implements the adapters for depositing unbalanced tokens on pots -/// of various pallets, e.g. Peaq-MOR, Peaq-Treasury etc. -macro_rules! impl_to_pot_adapter { - ($name:ident, $pot:ident, $negbal:ident) => { - pub struct $name; - impl OnUnbalanced<$negbal> for $name { - fn on_unbalanced(amount: $negbal) { - Self::on_nonzero_unbalanced(amount); - } +/// Sinks adopted by `migrations::v3::MigrateToV3x` when it finds the legacy, +/// pre-Sinks fixed distribution config on chain, and the default genesis sinks for +/// fresh `--chain dev-local` networks (see `node/src/parachain/dev_chain_spec.rs`). +/// Only treasury and parachain-staking survive the migration here, with a new split +/// -- everything the legacy config used to send to coretime/subsidization/DePIN +/// pots now goes to treasury instead. +/// +/// A `const` array (not a `parameter_types!` `Vec` literal) so the 100%-sum +/// invariant can be, and is, checked at compile time below. +const BLOCK_REWARD_SINKS: [pallet_block_reward::Sink; 2] = [ + pallet_block_reward::block_reward_sink!(PotTreasuryId, 70), + pallet_block_reward::block_reward_sink!(PotStakeId, 30), +]; +const _: () = assert!( + pallet_block_reward::is_complete_distribution(&BLOCK_REWARD_SINKS), + "BLOCK_REWARD_SINKS must sum to exactly 100% with no zero shares" +); - fn on_nonzero_unbalanced(amount: $negbal) { - let pot = $pot::get().into_account_truncating(); - Balances::resolve_creating(&pot, amount); - } - } - }; -} +parameter_types! { + /// Upper bound on the number of configurable block-reward token sinks. + pub const MaxBlockRewardSinks: u32 = 8; -impl_to_pot_adapter!(ToStakingPot, PotStakeId, NegativeImbalance); -impl_to_pot_adapter!(ToMachinePot, PotMorId, NegativeImbalance); -impl_to_pot_adapter!(ToCoreTimePot, PotCoretimeId, NegativeImbalance); -impl_to_pot_adapter!(ToSubsidizationPot, PotSubsidizationId, NegativeImbalance); -impl_to_pot_adapter!(ToDepinStakingPot, PotDepinStakingId, NegativeImbalance); + pub BlockRewardMigrationSinks: sp_std::vec::Vec = BLOCK_REWARD_SINKS.to_vec(); +} -pub struct ToTreasuryPot; -impl OnUnbalanced for ToTreasuryPot { - fn on_nonzero_unbalanced(amount: NegativeImbalance) { - let pot = PotTreasuryId::get().into_account_truncating(); - Balances::resolve_creating(&pot, amount); +/// Maps an EVM address to a Substrate `AccountId`, reusing the same hashing scheme +/// already used to unify EVM and native accounts elsewhere in this runtime. +pub struct BlockRewardAddressMapping; +impl pallet_block_reward::AddressMapping for BlockRewardAddressMapping { + fn into_account_id(address: H160) -> AccountId { + as pallet_evm::AddressMapping>::into_account_id( + address, + ) } } impl pallet_block_reward::Config for Runtime { + type AddressMapping = BlockRewardAddressMapping; type Currency = Balances; - type BeneficiaryPayout = BeneficiaryPayout; + type MaxSinks = MaxBlockRewardSinks; + type MigrationSinks = BlockRewardMigrationSinks; type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_block_reward::weights::WeightInfo; } -pub struct BeneficiaryPayout(); -impl pallet_block_reward::BeneficiaryPayout for BeneficiaryPayout { - fn treasury(reward: NegativeImbalance) { - ToTreasuryPot::on_unbalanced(reward); - } - - fn collators_delegators(reward: NegativeImbalance) { - ToStakingPot::on_unbalanced(reward); - } - - fn coretime(reward: NegativeImbalance) { - ToCoreTimePot::on_unbalanced(reward); - } - - fn subsidization_pool(reward: NegativeImbalance) { - ToSubsidizationPot::on_unbalanced(reward); - } - - fn depin_staking(reward: NegativeImbalance) { - ToDepinStakingPot::on_unbalanced(reward); - } - - fn depin_incentivization(reward: NegativeImbalance) { - let amount = reward.peek(); - ToMachinePot::on_unbalanced(reward); - PeaqMor::log_block_rewards(amount); - } -} - parameter_types! { pub const GetNativeAssetId: StorageAssetId = NATIVE_ASSET_ID; } @@ -2222,7 +2200,7 @@ impl_runtime_apis! { fn dispatch_benchmark( config: frame_benchmarking::BenchmarkConfig - ) -> Result, sp_runtime::RuntimeString> { + ) -> Result, alloc::string::String> { use frame_benchmarking::{baseline, BenchmarkBatch}; use frame_support::traits::TrackedStorageKey; use frame_system_benchmarking::Pallet as SystemBench; diff --git a/runtime/peaq/src/lib.rs b/runtime/peaq/src/lib.rs index 0229aa77..93109a7e 100644 --- a/runtime/peaq/src/lib.rs +++ b/runtime/peaq/src/lib.rs @@ -2,6 +2,8 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +extern crate alloc; + // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); @@ -210,7 +212,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 112, + spec_version: 113, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -917,72 +919,51 @@ impl parachain_staking::Config for Runtime { type WeightInfo = parachain_staking::weights::WeightInfo; } -/// Implements the adapters for depositing unbalanced tokens on pots -/// of various pallets, e.g. Peaq-MOR, Peaq-Treasury etc. -macro_rules! impl_to_pot_adapter { - ($name:ident, $pot:ident, $negbal:ident) => { - pub struct $name; - impl OnUnbalanced<$negbal> for $name { - fn on_unbalanced(amount: $negbal) { - Self::on_nonzero_unbalanced(amount); - } +/// Sinks adopted by `migrations::v3::MigrateToV3x` when it finds the legacy, +/// pre-Sinks fixed distribution config on chain, and the default genesis sinks for +/// fresh `--chain peaq-local` networks (see `node/src/parachain/peaq_chain_spec.rs`). +/// Only treasury and parachain-staking survive the migration here, with a new split +/// -- everything the legacy config used to send to coretime/subsidization/DePIN +/// pots now goes to treasury instead. +/// +/// A `const` array (not a `parameter_types!` `Vec` literal) so the 100%-sum +/// invariant can be, and is, checked at compile time below. +const BLOCK_REWARD_SINKS: [pallet_block_reward::Sink; 2] = [ + pallet_block_reward::block_reward_sink!(PotTreasuryId, 70), + pallet_block_reward::block_reward_sink!(PotStakeId, 30), +]; +const _: () = assert!( + pallet_block_reward::is_complete_distribution(&BLOCK_REWARD_SINKS), + "BLOCK_REWARD_SINKS must sum to exactly 100% with no zero shares" +); - fn on_nonzero_unbalanced(amount: $negbal) { - let pot = $pot::get().into_account_truncating(); - Balances::resolve_creating(&pot, amount); - } - } - }; -} +parameter_types! { + /// Upper bound on the number of configurable block-reward token sinks. + pub const MaxBlockRewardSinks: u32 = 8; -impl_to_pot_adapter!(ToStakingPot, PotStakeId, NegativeImbalance); -impl_to_pot_adapter!(ToCoreTimePot, PotCoretimeId, NegativeImbalance); -impl_to_pot_adapter!(ToSubsidizationPot, PotSubsidizationId, NegativeImbalance); -impl_to_pot_adapter!(ToDepinStakingPot, PotDepinStakingId, NegativeImbalance); -impl_to_pot_adapter!(ToDepinIncentivizationPot, PotDepinIncentivisationId, NegativeImbalance); + pub BlockRewardMigrationSinks: sp_std::vec::Vec = BLOCK_REWARD_SINKS.to_vec(); +} -pub struct ToTreasuryPot; -impl OnUnbalanced for ToTreasuryPot { - fn on_nonzero_unbalanced(amount: NegativeImbalance) { - let pot = PotTreasuryId::get().into_account_truncating(); - Balances::resolve_creating(&pot, amount); +/// Maps an EVM address to a Substrate `AccountId`, reusing the same hashing scheme +/// already used to unify EVM and native accounts elsewhere in this runtime. +pub struct BlockRewardAddressMapping; +impl pallet_block_reward::AddressMapping for BlockRewardAddressMapping { + fn into_account_id(address: H160) -> AccountId { + as pallet_evm::AddressMapping>::into_account_id( + address, + ) } } impl pallet_block_reward::Config for Runtime { + type AddressMapping = BlockRewardAddressMapping; type Currency = Balances; - type BeneficiaryPayout = BeneficiaryPayout; + type MaxSinks = MaxBlockRewardSinks; + type MigrationSinks = BlockRewardMigrationSinks; type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_block_reward::weights::WeightInfo; } -pub struct BeneficiaryPayout(); -impl pallet_block_reward::BeneficiaryPayout for BeneficiaryPayout { - fn treasury(reward: NegativeImbalance) { - ToTreasuryPot::on_unbalanced(reward); - } - - fn collators_delegators(reward: NegativeImbalance) { - ToStakingPot::on_unbalanced(reward); - } - - fn coretime(reward: NegativeImbalance) { - ToCoreTimePot::on_unbalanced(reward); - } - - fn subsidization_pool(reward: NegativeImbalance) { - ToSubsidizationPot::on_unbalanced(reward); - } - - fn depin_staking(reward: NegativeImbalance) { - ToDepinStakingPot::on_unbalanced(reward); - } - - fn depin_incentivization(reward: NegativeImbalance) { - ToDepinIncentivizationPot::on_unbalanced(reward); - } -} - parameter_types! { pub const GetNativeAssetId: StorageAssetId = NATIVE_ASSET_ID; } @@ -2238,7 +2219,7 @@ impl_runtime_apis! { fn dispatch_benchmark( config: frame_benchmarking::BenchmarkConfig - ) -> Result, sp_runtime::RuntimeString> { + ) -> Result, alloc::string::String> { use frame_benchmarking::{baseline, BenchmarkBatch}; use frame_support::traits::TrackedStorageKey; use frame_system_benchmarking::Pallet as SystemBench;