use core::marker::PhantomData; use alloc::{borrow::Cow, vec, vec::Vec}; use sp_core::{ConstU32, ConstU64, sr25519::Public}; use sp_runtime::{Perbill, Weight}; use sp_version::RuntimeVersion; use serai_abi::{ primitives::{ network_id::{ExternalNetworkId, NetworkId}, balance::{Amount, ExternalBalance}, validator_sets::{Session, ExternalValidatorSet, ValidatorSet}, address::SeraiAddress, }, SubstrateHeader as Header, SubstrateBlock, }; use serai_coins_pallet::{CoinsInstance, LiquidityTokensInstance}; type Block = SubstrateBlock; /// The lookup for a SeraiAddress -> Public. pub struct Lookup; impl sp_runtime::traits::StaticLookup for Lookup { type Source = SeraiAddress; type Target = Public; fn lookup(source: SeraiAddress) -> Result { Ok(source.into()) } fn unlookup(source: Public) -> SeraiAddress { source.into() } } // TODO: Remove #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: Cow::Borrowed("serai"), impl_name: Cow::Borrowed("core"), authoring_version: 0, spec_version: 0, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, system_version: 0, }; frame_support::parameter_types! { pub const Version: RuntimeVersion = VERSION; // TODO pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength::max_with_normal_ratio( 100 * 1024, Perbill::from_percent(75), ); // TODO pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::with_sensible_defaults( Weight::from_parts( 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX, ), Perbill::from_percent(75), ); } #[frame_support::runtime] mod runtime { use super::*; #[runtime::runtime] #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeError, RuntimeOrigin)] pub struct Runtime; #[runtime::pallet_index(0)] pub type System = frame_system::Pallet; #[runtime::pallet_index(1)] pub type Core = serai_core_pallet::Pallet; #[runtime::pallet_index(2)] pub type Coins = serai_coins_pallet::Pallet; #[runtime::pallet_index(3)] pub type ValidatorSets = serai_validator_sets_pallet::Pallet; #[runtime::pallet_index(4)] pub type Signals = serai_signals_pallet::Pallet; #[runtime::pallet_index(5)] pub type LiquidityTokens = serai_coins_pallet::Pallet; #[runtime::pallet_index(0xfe)] pub type Babe = pallet_babe::Pallet; #[runtime::pallet_index(0xff)] pub type Grandpa = pallet_grandpa::Pallet; } impl frame_system::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = BlockWeights; type BlockLength = BlockLength; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type RuntimeTask = (); type Nonce = u32; type Hash = ::Hash; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = sp_core::sr25519::Public; type Lookup = Lookup; type Block = Block; // Don't track old block hashes within the System pallet // We use not a number -> hash index, but a hash -> () index, in our own pallet type BlockHashCount = ConstU64<1>; type DbWeight = frame_support::weights::constants::RocksDbWeight; type Version = Version; type PalletInfo = PalletInfo; type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); // We use the default weights as we never expose/call any of these methods type SystemWeightInfo = (); // We also don't use the provided extensions framework type ExtensionsWeightInfo = (); // We don't invoke any hooks on-set-code as we don't perform upgrades via the blockchain yet via // nodes, ensuring everyone who upgrades consents to the rules they upgrade to type OnSetCode = (); type MaxConsumers = ConstU32<{ u32::MAX }>; // No migrations set type SingleBlockMigrations = (); type MultiBlockMigrator = (); type PreInherents = serai_core_pallet::StartOfBlock; type PostInherents = (); type PostTransactions = serai_core_pallet::EndOfBlock; } impl serai_core_pallet::Config for Runtime {} impl serai_coins_pallet::Config for Runtime { type AllowMint = serai_coins_pallet::AlwaysAllowMint; // TODO } #[doc(hidden)] pub struct EconomicSecurity; // TODO impl serai_abi::economic_security::EconomicSecurity for EconomicSecurity { fn achieved_economic_security(_network: ExternalNetworkId) -> bool { false } fn sri_value(_balance: ExternalBalance) -> Amount { Amount(0) } } impl serai_validator_sets_pallet::Config for Runtime { type ShouldEndSession = Babe; type EconomicSecurity = EconomicSecurity; } impl serai_signals_pallet::Config for Runtime { type RetirementValidityDuration = ConstU64<0>; // TODO type RetirementLockInDuration = ConstU64<1>; // TODO } impl serai_coins_pallet::Config for Runtime { type AllowMint = serai_coins_pallet::AlwaysAllowMint; } /* `pallet-babe` requires we implement `pallet-timestamp` for the associated constants. It does not actually require we offer the timestamp pallet however, and we don't as we follow our methodology (using the block header for timestamps, not an inherent transaction). TODO: Set timestamp when executing a block. */ impl pallet_timestamp::Config for Runtime { type Moment = u64; type OnTimestampSet = Babe; // TODO type MinimumPeriod = ConstU64<{ (6 * 1000) / 2 }>; type WeightInfo = (); } #[doc(hidden)] pub struct GetCurrentSessionForSubstrate; impl pallet_session::GetCurrentSessionForSubstrate for GetCurrentSessionForSubstrate { fn get() -> u32 { serai_validator_sets_pallet::Pallet::::current_session(NetworkId::Serai) .map(|session| session.0) .unwrap_or(0) } } impl pallet_session::Config for Runtime { type Session = GetCurrentSessionForSubstrate; } type MaxAuthorities = ConstU32<{ serai_abi::primitives::validator_sets::KeyShares::MAX_PER_SET_U32 }>; impl pallet_babe::Config for Runtime { // TODO type EpochDuration = ConstU64<{ (7 * 24 * 60 * 60 * 1000) / (6 * 1000) }>; type ExpectedBlockTime = ConstU64<{ 6 * 1000 }>; // TODO type EpochChangeTrigger = pallet_babe::ExternalTrigger; type WeightInfo = (); type MaxAuthorities = MaxAuthorities; type MaxNominators = ConstU32<1>; // TODO: https://github.com/serai-dex/serai/issues/657 type DisabledValidators = (); type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = (); } impl pallet_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type MaxAuthorities = MaxAuthorities; type MaxNominators = ConstU32<1>; // TODO: https://github.com/serai-dex/serai/issues/657 type MaxSetIdSessionEntries = ConstU64<0>; type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = (); } impl From> for RuntimeOrigin { fn from(signer: Option) -> Self { match signer { None => RuntimeOrigin::none(), Some(signer) => RuntimeOrigin::signed(signer.into()), } } } impl From for RuntimeCall { fn from(call: serai_abi::Call) -> Self { match call { serai_abi::Call::Coins(call) => { use serai_abi::coins::Call; use serai_coins_pallet::Call as Scall; RuntimeCall::Coins(match call { Call::transfer { to, coins } => Scall::transfer { to: to.into(), coins }, Call::burn { coins } => Scall::burn { coins }, Call::burn_with_instruction { instruction } => { Scall::burn_with_instruction { instruction } } }) } serai_abi::Call::ValidatorSets(call) => { use serai_abi::validator_sets::Call; use serai_validator_sets_pallet::Call as Scall; RuntimeCall::ValidatorSets(match call { Call::set_keys { network, key_pair, signature_participants, signature } => { Scall::set_keys { network, key_pair, signature_participants, signature } } Call::report_slashes { network, slashes, signature } => { Scall::report_slashes { network, slashes, signature } } Call::set_embedded_elliptic_curve_keys { keys } => { Scall::set_embedded_elliptic_curve_keys { keys } } Call::allocate { network, amount } => Scall::allocate { network, amount }, Call::deallocate { network, amount } => Scall::deallocate { network, amount }, Call::claim_deallocation { deallocation } => Scall::claim_deallocation { network: deallocation.network, session: deallocation.session, }, }) } serai_abi::Call::Signals(call) => { use serai_abi::signals::Call; use serai_signals_pallet::Call as Scall; RuntimeCall::Signals(match call { Call::register_retirement_signal { in_favor_of } => { Scall::register_retirement_signal { in_favor_of } } Call::revoke_retirement_signal { was_in_favor_of } => { Scall::revoke_retirement_signal { retirement_signal: was_in_favor_of } } Call::favor { signal, with_network } => Scall::favor { signal, with_network }, Call::revoke_favor { signal, with_network } => { Scall::revoke_favor { signal, with_network } } Call::stand_against { signal, with_network } => { Scall::stand_against { signal, with_network } } }) } serai_abi::Call::Dex(call) => { use serai_abi::dex::Call; match call { Call::transfer_liquidity { to, liquidity_tokens } => { RuntimeCall::LiquidityTokens(serai_coins_pallet::Call::transfer { to: to.into(), coins: liquidity_tokens.into(), }) } Call::add_liquidity { .. } | Call::remove_liquidity { .. } | Call::swap_exact { .. } | Call::swap_for_exact { .. } => todo!("TODO"), } } serai_abi::Call::GenesisLiquidity(call) => { use serai_abi::genesis_liquidity::Call; match call { Call::oraclize_values { .. } | Call::remove_liquidity { .. } => todo!("TODO"), } } serai_abi::Call::InInstructions(call) => { use serai_abi::in_instructions::Call; match call { Call::execute_batch { .. } => todo!("TODO"), } } } } } type Executive = frame_executive::Executive; const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); pub const BABE_GENESIS_EPOCH_CONFIG: sp_consensus_babe::BabeEpochConfiguration = sp_consensus_babe::BabeEpochConfiguration { c: PRIMARY_PROBABILITY, allowed_slots: sp_consensus_babe::AllowedSlots::PrimaryAndSecondaryPlainSlots, }; sp_api::impl_runtime_apis! { impl crate::GenesisApi for Runtime { fn build(genesis: crate::GenesisConfig) { let config = RuntimeGenesisConfig { system: SystemConfig { _config: PhantomData }, coins: CoinsConfig { accounts: genesis.coins.into_iter().map(|(key, balance)| (key.into(), balance)).collect(), _instance: PhantomData, }, liquidity_tokens: LiquidityTokensConfig { accounts: vec![], _instance: PhantomData }, validator_sets: ValidatorSetsConfig { participants: genesis.validators.into_iter().map(|(key, keys)| (key.into(), keys)).collect(), }, signals: SignalsConfig::default(), // We leave these `authorities` empty as `serai-validator-sets-pallet` initializes them babe: BabeConfig { authorities: vec![], epoch_config: BABE_GENESIS_EPOCH_CONFIG, _config: PhantomData, }, grandpa: GrandpaConfig { authorities: vec![], _config: PhantomData }, }; Core::genesis(); Core::start_transaction(); ::build(&config); Core::end_transaction([0; 32]); < serai_core_pallet::EndOfBlock as frame_support::traits::PostTransactions >::post_transactions(); } } impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION } fn initialize_block(header: &Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } fn execute_block(block: Block) { Executive::execute_block(block); } } impl sp_block_builder::BlockBuilder for Runtime { fn apply_extrinsic( extrinsic: ::Extrinsic, ) -> sp_runtime::ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) } fn finalize_block() -> Header { Executive::finalize_block() } fn inherent_extrinsics( data: sp_inherents::InherentData, ) -> Vec<::Extrinsic> { data.create_extrinsics() } fn check_inherents( block: Block, data: sp_inherents::InherentData, ) -> sp_inherents::CheckInherentsResult { data.check_extrinsics(&block) } } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { fn validate_transaction( source: sp_runtime::transaction_validity::TransactionSource, tx: ::Extrinsic, block_hash: ::Hash, ) -> sp_runtime::transaction_validity::TransactionValidity { Executive::validate_transaction(source, tx, block_hash) } } impl sp_consensus_babe::BabeApi for Runtime { fn configuration() -> sp_consensus_babe::BabeConfiguration { use frame_support::traits::Get; let epoch_config = Babe::epoch_config().unwrap_or(BABE_GENESIS_EPOCH_CONFIG); sp_consensus_babe::BabeConfiguration { slot_duration: Babe::slot_duration(), epoch_length: ::EpochDuration::get(), c: epoch_config.c, authorities: Babe::authorities().to_vec(), randomness: Babe::randomness(), allowed_slots: epoch_config.allowed_slots, } } fn current_epoch_start() -> sp_consensus_babe::Slot { Babe::current_epoch_start() } fn current_epoch() -> sp_consensus_babe::Epoch { Babe::current_epoch() } fn next_epoch() -> sp_consensus_babe::Epoch { Babe::next_epoch() } // TODO: Revisit fn generate_key_ownership_proof( _slot: sp_consensus_babe::Slot, _authority_id: sp_consensus_babe::AuthorityId, ) -> Option { None } // TODO: Revisit fn submit_report_equivocation_unsigned_extrinsic( equivocation_proof: sp_consensus_babe::EquivocationProof
, _: sp_consensus_babe::OpaqueKeyOwnershipProof, ) -> Option<()> { None } } impl sp_consensus_grandpa::GrandpaApi for Runtime { fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList { Grandpa::grandpa_authorities() } fn current_set_id() -> sp_consensus_grandpa::SetId { Grandpa::current_set_id() } // TODO: Revisit fn generate_key_ownership_proof( _set_id: sp_consensus_grandpa::SetId, _authority_id: sp_consensus_grandpa::AuthorityId, ) -> Option { None } // TODO: Revisit fn submit_report_equivocation_unsigned_extrinsic( equivocation_proof: sp_consensus_grandpa::EquivocationProof< ::Hash, u64, >, _: sp_consensus_grandpa::OpaqueKeyOwnershipProof, ) -> Option<()> { None } } impl sp_authority_discovery::AuthorityDiscoveryApi for Runtime { fn authorities() -> Vec { // Converts to `[u8; 32]` so it can be hashed let mut all = alloc::collections::BTreeSet::<[u8; 32]>::new(); for network in NetworkId::all() { for participant in >::validators(network) { all.insert(sp_core::sr25519::Public::from(participant).into()); } } all .into_iter() .map(|id| sp_authority_discovery::AuthorityId::from(sp_core::sr25519::Public::from(id))) .collect() } } impl crate::SeraiApi for Runtime { fn events() -> Vec> { Core::events() } fn validators(network: NetworkId) -> Vec { // Returning the latest-decided, not latest and active, means the active set // may fail to peer find if there isn't sufficient overlap. If a large amount reboot, // forcing some validators to successfully peer find in order for the threshold to become // online again, this may cause a liveness failure. // // This is assumed not to matter in real life, yet an interesting note. let Some(session) = ValidatorSets::latest_decided_session(network) else { return vec![] }; ValidatorSets::selected_validators(ValidatorSet { network, session }) .map(|validator| validator.0.into()) .collect() } fn current_session(network: NetworkId) -> Option { ValidatorSets::current_session(network) } fn current_stake(network: NetworkId) -> Option { ValidatorSets::stake_for_current_validator_set(network) } fn keys(set: ExternalValidatorSet) -> Option { ValidatorSets::oraclization_key(set) .and_then(|oraclization_key| { ValidatorSets::external_key(set) .map(|external_key| { serai_abi::primitives::crypto::KeyPair(oraclization_key.into(), external_key) }) }) } } } #[derive(Clone, Default, PartialEq, Eq, Debug)] struct Context; impl serai_abi::TransactionContext for Context { // TODO const SIGNED_WEIGHT: Weight = Weight::zero(); type RuntimeCall = RuntimeCall; /// The implicit context to verify transactions with. fn implicit_context() -> serai_abi::ImplicitContext { serai_abi::ImplicitContext { genesis: System::block_hash(0).into(), protocol_id: [0; 32], // TODO via build script } } /// If a block is present in the blockchain. fn block_is_present_in_blockchain(&self, hash: &serai_abi::primitives::BlockHash) -> bool { serai_core_pallet::Pallet::::block_exists(hash) } /// The time embedded into the current block. fn current_time(&self) -> Option { todo!("TODO") } /// Get the next nonce for an account. fn next_nonce(&self, signer: &SeraiAddress) -> u32 { serai_core_pallet::Pallet::::next_nonce(signer) } /// If the signer can pay the SRI fee. fn can_pay_fee( &self, signer: &SeraiAddress, fee: serai_abi::primitives::balance::Amount, ) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> { use serai_abi::primitives::coin::Coin; if serai_coins_pallet::Pallet::::balance(signer, Coin::Serai) >= fee { Ok(()) } else { Err(sp_runtime::transaction_validity::TransactionValidityError::Invalid( sp_runtime::transaction_validity::InvalidTransaction::Payment, )) } } fn start_transaction(&self) { Core::start_transaction() } fn consume_next_nonce(&self, signer: &SeraiAddress) { serai_core_pallet::Pallet::::consume_next_nonce(signer) } /// Have the transaction pay its SRI fee. fn pay_fee( &self, signer: &SeraiAddress, fee: serai_abi::primitives::balance::Amount, ) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> { use serai_abi::primitives::{coin::*, balance::*}; serai_coins_pallet::Pallet::::burn( RuntimeOrigin::signed(Public::from(*signer)), Balance { coin: Coin::Serai, amount: fee }, ) .map_err(|_| { sp_runtime::transaction_validity::TransactionValidityError::Invalid( sp_runtime::transaction_validity::InvalidTransaction::Payment, ) }) } fn end_transaction(&self, transaction_hash: [u8; 32]) { Core::end_transaction(transaction_hash); } } /* TODO use validator_sets::MembershipProof; const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); parameter_types! { pub const Version: RuntimeVersion = VERSION; pub const SS58Prefix: u8 = 42; // TODO: Remove for Bech32m // 1 MB block size limit pub BlockLength: system::limits::BlockLength = system::limits::BlockLength::max_with_normal_ratio(BLOCK_SIZE, NORMAL_DISPATCH_RATIO); pub BlockWeights: system::limits::BlockWeights = system::limits::BlockWeights::with_sensible_defaults( Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), NORMAL_DISPATCH_RATIO, ); } impl timestamp::Config for Runtime { type Moment = u64; type OnTimestampSet = Babe; type MinimumPeriod = ConstU64<{ (TARGET_BLOCK_TIME * 1000) / 2 }>; type WeightInfo = (); } impl coins::Config for Runtime { type AllowMint = ValidatorSets; } impl dex::Config for Runtime { type LPFee = ConstU32<3>; // 0.3% type MintMinLiquidity = ConstU64<10000>; type MaxSwapPathLength = ConstU32<3>; // coin1 -> SRI -> coin2 type MedianPriceWindowLength = ConstU16<{ MEDIAN_PRICE_WINDOW_LENGTH }>; type WeightInfo = dex::weights::SubstrateWeight; } pub struct IdentityValidatorIdOf; impl Convert> for IdentityValidatorIdOf { fn convert(key: PublicKey) -> Option { Some(key) } } impl signals::Config for Runtime { type RuntimeEvent = RuntimeEvent; // 1 week #[allow(clippy::cast_possible_truncation)] type RetirementValidityDuration = ConstU32<{ (7 * 24 * 60 * 60) / (TARGET_BLOCK_TIME as u32) }>; // 2 weeks #[allow(clippy::cast_possible_truncation)] type RetirementLockInDuration = ConstU32<{ (2 * 7 * 24 * 60 * 60) / (TARGET_BLOCK_TIME as u32) }>; } impl in_instructions::Config for Runtime { type RuntimeEvent = RuntimeEvent; } impl genesis_liquidity::Config for Runtime { type RuntimeEvent = RuntimeEvent; } impl emissions::Config for Runtime { type RuntimeEvent = RuntimeEvent; } impl economic_security::Config for Runtime { type RuntimeEvent = RuntimeEvent; } // for publishing equivocation evidences. impl frame_system::offchain::SendTransactionTypes for Runtime where RuntimeCall: From, { type Extrinsic = Transaction; type OverarchingCall = RuntimeCall; } // for validating equivocation evidences. // The following runtime construction doesn't actually implement the pallet as doing so is // unnecessary // TODO: Replace the requirement on Config for a requirement on FindAuthor directly impl pallet_authorship::Config for Runtime { type FindAuthor = ValidatorSets; type EventHandler = (); } /// Longevity of an offence report. pub type ReportLongevity = ::EpochDuration; #[cfg(feature = "runtime-benchmarks")] #[macro_use] extern crate frame_benchmarking; #[cfg(feature = "runtime-benchmarks")] mod benches { define_benchmarks!( [frame_benchmarking, BaselineBench::] [system, SystemBench::] [pallet_timestamp, Timestamp] [balances, Balances] [babe, Babe] [grandpa, Grandpa] ); } sp_api::impl_runtime_apis! { impl validator_sets::ValidatorSetsApi for Runtime { fn external_network_key(network: ExternalNetworkId) -> Option> { ValidatorSets::external_network_key(network) } } impl dex::DexApi for Runtime { fn quote_price_exact_tokens_for_tokens( coin1: Coin, coin2: Coin, amount: SubstrateAmount, include_fee: bool ) -> Option { Dex::quote_price_exact_tokens_for_tokens(coin1, coin2, amount, include_fee) } fn quote_price_tokens_for_exact_tokens( coin1: Coin, coin2: Coin, amount: SubstrateAmount, include_fee: bool ) -> Option { Dex::quote_price_tokens_for_exact_tokens(coin1, coin2, amount, include_fee) } fn get_reserves(coin1: Coin, coin2: Coin) -> Option<(SubstrateAmount, SubstrateAmount)> { Dex::get_reserves(&coin1, &coin2).ok() } } } */