diff --git a/Cargo.lock b/Cargo.lock index 5aa8400b..e76d7dba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10243,7 +10243,7 @@ dependencies = [ "chrono", "lazy_static", "matchers", - "parking_lot 0.9.0", + "parking_lot 0.11.2", "regex", "serde", "serde_json", diff --git a/README.md b/README.md index 8e4fe346..d044ffc0 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ consensus engine simply by creating filters that implement the `CanAuthor` trait This repository comes with a few example filters already, and additional examples are welcome. The examples are: * PseudoRandom FixedSized Subset - This filter takes a finite set (eg a staked set) and filters it down to a pseudo-random -subset at each height. The eligible ratio is configurable in the pallet. This is a good learning example. +subset at each height. The eligible count is configurable in the pallet. This is a good learning example. * Aura - The authority round consensus engine is popular in the Substrate ecosystem because it was one of the first (and simplest!) engines implemented in Substrate. Aura can be expressed in the Nimbus filter framework and is included as an example filter. If you are considering using aura, that crate diff --git a/pallets/author-slot-filter/Cargo.toml b/pallets/author-slot-filter/Cargo.toml index 68210068..61500e13 100644 --- a/pallets/author-slot-filter/Cargo.toml +++ b/pallets/author-slot-filter/Cargo.toml @@ -12,7 +12,7 @@ log = { version = "0.4", default-features = false } nimbus-primitives = { path = "../../nimbus-primitives", default-features = false } parity-scale-codec = { version = "2.0.0", default-features = false, features = [ "derive" ] } scale-info = { version = "1.0.0", default-features = false, features = [ "derive" ] } -serde = { version = "1.0.101", optional = true, features = [ "derive" ] } +serde = { version = "1.0.101", default-features = false, features = [ "derive" ] } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -24,7 +24,6 @@ frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = frame-support-test = { version = "3.0.0", git = "https://github.com/paritytech/substrate", branch = "master" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master"} - [features] default = [ "std" ] std = [ @@ -35,7 +34,7 @@ std = [ "nimbus-primitives/std", "parity-scale-codec/std", "scale-info/std", - "serde", + "serde/std", "sp-core/std", "sp-runtime/std", "sp-std/std", diff --git a/pallets/author-slot-filter/src/lib.rs b/pallets/author-slot-filter/src/lib.rs index 44d5100e..d4c812cb 100644 --- a/pallets/author-slot-filter/src/lib.rs +++ b/pallets/author-slot-filter/src/lib.rs @@ -32,11 +32,19 @@ pub use pallet::*; #[cfg(any(test, feature = "runtime-benchmarks"))] mod benchmarks; +pub mod migration; +pub mod num; pub mod weights; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + #[pallet] pub mod pallet { + use crate::num::NonZeroU32; use crate::weights::WeightInfo; use frame_support::{pallet_prelude::*, traits::Randomness}; use frame_system::pallet_prelude::*; @@ -74,7 +82,11 @@ pub mod pallet { mut active: Vec, seed: &u32, ) -> (Vec, Vec) { - let num_eligible = EligibleRatio::::get().mul_ceil(active.len()); + let mut num_eligible = EligibleCount::::get().get() as usize; + if num_eligible > active.len() { + num_eligible = active.len(); + } + let mut eligible = Vec::with_capacity(num_eligible); for i in 0..num_eligible { @@ -128,20 +140,26 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Update the eligible ratio. Intended to be called by governance. + /// Update the eligible count. Intended to be called by governance. #[pallet::weight(T::WeightInfo::set_eligible())] - pub fn set_eligible(origin: OriginFor, new: Percent) -> DispatchResultWithPostInfo { + pub fn set_eligible( + origin: OriginFor, + new: EligibilityValue, + ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - EligibleRatio::::put(&new); + EligibleCount::::put(&new); >::deposit_event(Event::EligibleUpdated(new)); Ok(Default::default()) } } - /// The percentage of active authors that will be eligible at each height. + /// The type of eligibility to use + pub type EligibilityValue = NonZeroU32; + #[pallet::storage] #[pallet::getter(fn eligible_ratio)] + #[deprecated] pub type EligibleRatio = StorageValue<_, Percent, ValueQuery, Half>; // Default value for the `EligibleRatio` is one half. @@ -150,16 +168,31 @@ pub mod pallet { Percent::from_percent(50) } + /// The number of active authors that will be eligible at each height. + #[pallet::storage] + #[pallet::getter(fn eligible_count)] + pub type EligibleCount = + StorageValue<_, EligibilityValue, ValueQuery, DefaultEligibilityValue>; + + /// Default total number of eligible authors, must NOT be 0. + pub const DEFAULT_TOTAL_ELIGIBLE_AUTHORS: EligibilityValue = NonZeroU32::new_unchecked(50); + + // Default value for the `EligibleCount`. + #[pallet::type_value] + pub fn DefaultEligibilityValue() -> EligibilityValue { + DEFAULT_TOTAL_ELIGIBLE_AUTHORS + } + #[pallet::genesis_config] pub struct GenesisConfig { - pub eligible_ratio: Percent, + pub eligible_count: EligibilityValue, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { Self { - eligible_ratio: Percent::from_percent(50), + eligible_count: DEFAULT_TOTAL_ELIGIBLE_AUTHORS, } } } @@ -167,7 +200,7 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - EligibleRatio::::put(self.eligible_ratio); + EligibleCount::::put(self.eligible_count.clone()); } } @@ -175,94 +208,6 @@ pub mod pallet { #[pallet::generate_deposit(fn deposit_event)] pub enum Event { /// The amount of eligible authors for the filter to select has been changed. - EligibleUpdated(Percent), - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate as author_slot_filter; - - use frame_support::{assert_ok, parameter_types, traits::Everything}; - use frame_support_test::TestRandomness; - use sp_core::H256; - use sp_io::TestExternalities; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - Percent, - }; - use sp_std::vec; - const AUTHOR_ID: u64 = 1; - - pub fn new_test_ext() -> TestExternalities { - let t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - TestExternalities::new(t) - } - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - // Configure a mock runtime to test the pallet. - frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - AuthorSlotFilter: author_slot_filter::{Pallet, Call, Storage, Event, Config}, - } - ); - - parameter_types! { - pub const BlockHashCount: u64 = 250; - } - impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Call = Call; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - } - parameter_types! { - pub Authors: Vec = vec![AUTHOR_ID]; - } - impl Config for Test { - type Event = Event; - type RandomnessSource = TestRandomness; - type PotentialAuthors = Authors; - type WeightInfo = (); - } - - #[test] - fn set_eligibility_works() { - new_test_ext().execute_with(|| { - let percent = Percent::from_percent(34); - - assert_ok!(AuthorSlotFilter::set_eligible(Origin::root(), percent)); - assert_eq!(AuthorSlotFilter::eligible_ratio(), percent) - }); + EligibleUpdated(EligibilityValue), } } diff --git a/pallets/author-slot-filter/src/migration.rs b/pallets/author-slot-filter/src/migration.rs new file mode 100644 index 00000000..65ab51bc --- /dev/null +++ b/pallets/author-slot-filter/src/migration.rs @@ -0,0 +1,86 @@ +// Copyright 2019-2021 PureStake Inc. +// This file is part of Nimbus. + +// Nimbus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Nimbus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Nimbus. If not, see . + +use core::marker::PhantomData; +use frame_support::storage::migration; +use frame_support::traits::Get; +use frame_support::traits::OnRuntimeUpgrade; +use frame_support::weights::Weight; +use sp_runtime::Percent; + +#[cfg(feature = "try-runtime")] +use frame_support::traits::OnRuntimeUpgradeHelpersExt; + +use super::num::NonZeroU32; +use super::pallet::Config; + +pub struct EligibleRatioToEligiblityCount(PhantomData); + +pub const PALLET_NAME: &[u8] = b"AuthorSlotFilter"; +pub const ELIGIBLE_RATIO_ITEM_NAME: &[u8] = b"EligibleRatio"; +pub const ELIGIBLE_COUNT_ITEM_NAME: &[u8] = b"EligibleCount"; + +impl OnRuntimeUpgrade for EligibleRatioToEligiblityCount +where + T: Config, +{ + fn on_runtime_upgrade() -> Weight { + log::info!(target: "EligibleRatioToEligiblityCount", "starting migration"); + + if let Some(old_value) = + migration::get_storage_value::(PALLET_NAME, ELIGIBLE_RATIO_ITEM_NAME, &[]) + { + let total_authors = ::PotentialAuthors::get().len(); + let new_value: u32 = percent_of_num(old_value, total_authors as u32); + migration::put_storage_value( + PALLET_NAME, + ELIGIBLE_COUNT_ITEM_NAME, + &[], + NonZeroU32::new(new_value).unwrap_or(crate::pallet::DEFAULT_TOTAL_ELIGIBLE_AUTHORS), + ); + + let db_weights = T::DbWeight::get(); + db_weights.write + db_weights.read + } else { + 0 + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + if let Some(eligible_ratio) = + migration::get_storage_value::(PALLET_NAME, ELIGIBLE_RATIO_ITEM_NAME, &[]) + { + let total_authors = ::PotentialAuthors::get().len(); + let eligible_count: u32 = percent_of_num(eligible_ratio, total_authors as u32); + let eligible_count = NonZeroU32::new_unchecked(eligible_count); + Self::set_temp_storage(new_value, "expected_eligible_count"); + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + let expected = Self::get_temp_storage::("expected_eligible_count"); + let actual = + migration::get_storage_value::(PALLET_NAME, ELIGIBLE_COUNT_ITEM_NAME, &[]); + + assert_eq!(expected, actual); + } +} + +fn percent_of_num(percent: Percent, num: u32) -> u32 { + percent.mul_ceil(num as u32) +} diff --git a/pallets/author-slot-filter/src/mock.rs b/pallets/author-slot-filter/src/mock.rs new file mode 100644 index 00000000..7178525b --- /dev/null +++ b/pallets/author-slot-filter/src/mock.rs @@ -0,0 +1,91 @@ +// Copyright 2019-2021 PureStake Inc. +// This file is part of Nimbus. + +// Nimbus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Nimbus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Nimbus. If not, see . + +use crate as pallet_testing; +use frame_support::parameter_types; +use frame_support::sp_io; +use frame_support::weights::RuntimeDbWeight; +use frame_support_test::TestRandomness; +use frame_system; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + AuthorSlotFilter: pallet_testing::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub Authors: Vec = vec![1, 2, 3, 4, 5]; + pub const TestDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 1, + write: 10, + }; +} + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = TestDbWeight; + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +impl pallet_testing::Config for Test { + type Event = Event; + type RandomnessSource = TestRandomness; + type PotentialAuthors = Authors; + type WeightInfo = (); +} + +/// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into() +} diff --git a/pallets/author-slot-filter/src/num.rs b/pallets/author-slot-filter/src/num.rs new file mode 100644 index 00000000..1d846ba7 --- /dev/null +++ b/pallets/author-slot-filter/src/num.rs @@ -0,0 +1,127 @@ +// Copyright 2019-2021 PureStake Inc. +// This file is part of Nimbus. + +// Nimbus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Nimbus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Nimbus. If not, see . + +//! Implements a [NonZeroU32] type that interplays nicely with the +//! subtrate storage and the SCALE codec. + +use parity_scale_codec::{Decode, Encode, Error, Input}; +use scale_info::TypeInfo; +use serde::de::Error as DeserializeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Clone, Debug, TypeInfo, Encode, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct NonZeroU32(u32); + +impl core::ops::Deref for NonZeroU32 { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl parity_scale_codec::EncodeLike for NonZeroU32 {} + +impl NonZeroU32 { + /// Creates a new `Some(NonZeroU32)` instance if value is 0, `None` otherwise. + #[inline] + pub const fn new(n: u32) -> Option { + if n != 0 { + Some(Self(n)) + } else { + None + } + } + + /// new_unchecked creats a `NonZeroU32` where the user MUST guarantee + /// that the value is nonzero. + #[inline] + pub const fn new_unchecked(n: u32) -> Self { + Self(n) + } + + /// Returns the the underlying number + pub fn get(&self) -> u32 { + self.0 + } +} + +#[cfg(feature = "std")] +impl Serialize for NonZeroU32 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.clone().get().serialize(serializer) + } +} + +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for NonZeroU32 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = Deserialize::deserialize(deserializer)?; + match NonZeroU32::new(value) { + Some(nonzero) => Ok(nonzero), + None => Err(DeserializeError::custom("expected a non-zero value")), + } + } +} + +impl Decode for NonZeroU32 { + fn decode(input: &mut I) -> Result { + Self::new(Decode::decode(input)?) + .ok_or_else(|| Error::from("cannot create non-zero number from 0")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use parity_scale_codec::Encode; + + #[test] + fn test_new_returns_none_if_zero() { + assert_eq!(None, NonZeroU32::new(0)); + } + + #[test] + fn test_new_returns_some_if_nonzero() { + let n = 10; + let expected = Some(NonZeroU32::new_unchecked(n)); + + let actual = NonZeroU32::new(n); + assert_eq!(expected, actual); + assert_eq!(n, actual.unwrap().get()); + } + + #[test] + fn test_decode_errors_if_zero_value() { + let buf: Vec = 0u32.encode(); + let result = NonZeroU32::decode(&mut &buf[..]); + assert!(result.is_err(), "expected error, got {:?}", result); + } + + #[test] + fn test_decode_succeeds_if_nonzero_value() { + let buf: Vec = 1u32.encode(); + + let result = NonZeroU32::decode(&mut &buf[..]); + assert!(result.is_ok(), "unexpected error, got {:?}", result); + assert_eq!(Ok(NonZeroU32::new_unchecked(1)), result); + } +} diff --git a/pallets/author-slot-filter/src/tests.rs b/pallets/author-slot-filter/src/tests.rs new file mode 100644 index 00000000..e4bc2c3d --- /dev/null +++ b/pallets/author-slot-filter/src/tests.rs @@ -0,0 +1,103 @@ +// Copyright 2019-2021 PureStake Inc. +// This file is part of Nimbus. + +// Nimbus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Nimbus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Nimbus. If not, see . + +use super::*; +use crate::mock::*; +use crate::num::NonZeroU32; + +use frame_support::assert_ok; +use frame_support::migration::put_storage_value; +use frame_support::traits::OnRuntimeUpgrade; +use sp_runtime::Percent; + +#[test] +fn test_set_eligibility_works() { + new_test_ext().execute_with(|| { + let value = num::NonZeroU32::new_unchecked(34); + + assert_ok!(AuthorSlotFilter::set_eligible( + Origin::root(), + value.clone() + )); + assert_eq!(AuthorSlotFilter::eligible_count(), value) + }); +} + +#[test] +fn test_migration_works_for_converting_existing_eligible_ratio_to_eligible_count() { + new_test_ext().execute_with(|| { + let input_eligible_ratio = Percent::from_percent(50); + let total_author_count = mock::Authors::get().len(); + let eligible_author_count = + input_eligible_ratio.clone().mul_ceil(total_author_count) as u32; + let expected_eligible_count = NonZeroU32::new_unchecked(eligible_author_count); + let expected_weight = TestDbWeight::get().write + TestDbWeight::get().read; + + put_storage_value( + migration::PALLET_NAME, + migration::ELIGIBLE_RATIO_ITEM_NAME, + &[], + input_eligible_ratio.clone(), + ); + + let actual_weight = migration::EligibleRatioToEligiblityCount::::on_runtime_upgrade(); + assert_eq!(expected_weight, actual_weight); + + let actual_eligible_ratio_after = AuthorSlotFilter::eligible_ratio(); + let actual_eligible_count = AuthorSlotFilter::eligible_count(); + assert_eq!(expected_eligible_count, actual_eligible_count); + assert_eq!(input_eligible_ratio, actual_eligible_ratio_after); + }); +} + +#[test] +fn test_migration_works_for_converting_existing_zero_eligible_ratio_to_default_eligible_count() { + new_test_ext().execute_with(|| { + let input_eligible_ratio = Percent::from_percent(0); + let expected_eligible_count = DEFAULT_TOTAL_ELIGIBLE_AUTHORS; + let expected_weight = TestDbWeight::get().write + TestDbWeight::get().read; + + put_storage_value( + migration::PALLET_NAME, + migration::ELIGIBLE_RATIO_ITEM_NAME, + &[], + input_eligible_ratio.clone(), + ); + + let actual_weight = migration::EligibleRatioToEligiblityCount::::on_runtime_upgrade(); + assert_eq!(expected_weight, actual_weight); + + let actual_eligible_ratio_after = AuthorSlotFilter::eligible_ratio(); + let actual_eligible_count = AuthorSlotFilter::eligible_count(); + assert_eq!(expected_eligible_count, actual_eligible_count); + assert_eq!(input_eligible_ratio, actual_eligible_ratio_after); + }); +} + +#[test] +fn test_migration_skips_converting_missing_eligible_ratio_to_eligible_count_and_returns_default_value( +) { + new_test_ext().execute_with(|| { + let expected_default_eligible_count = DEFAULT_TOTAL_ELIGIBLE_AUTHORS; + let expected_weight = 0; + + let actual_weight = migration::EligibleRatioToEligiblityCount::::on_runtime_upgrade(); + assert_eq!(expected_weight, actual_weight); + + let actual_eligible_count = AuthorSlotFilter::eligible_count(); + assert_eq!(expected_default_eligible_count, actual_eligible_count); + }); +} diff --git a/parachain-template/node/src/chain_spec.rs b/parachain-template/node/src/chain_spec.rs index b2d312ef..ba33f597 100644 --- a/parachain-template/node/src/chain_spec.rs +++ b/parachain-template/node/src/chain_spec.rs @@ -178,7 +178,7 @@ fn testnet_genesis( }, parachain_info: parachain_template_runtime::ParachainInfoConfig { parachain_id: id }, author_filter: parachain_template_runtime::AuthorFilterConfig { - eligible_ratio: sp_runtime::Percent::from_percent(50), + eligible_count: parachain_template_runtime::DEFAULT_TOTAL_ELIGIBLE_AUTHORS, }, potential_author_set: parachain_template_runtime::PotentialAuthorSetConfig { mapping: authorities, diff --git a/parachain-template/runtime/src/lib.rs b/parachain-template/runtime/src/lib.rs index ba8fa76e..56eabff1 100644 --- a/parachain-template/runtime/src/lib.rs +++ b/parachain-template/runtime/src/lib.rs @@ -18,6 +18,8 @@ use sp_runtime::{ pub use nimbus_primitives::NimbusId; +pub use pallet_author_slot_filter::DEFAULT_TOTAL_ELIGIBLE_AUTHORS; + use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion;