// Copyright (C) 2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Auxiliary struct/enums for parachain runtimes. //! Taken from polkadot/runtime/common (at a21cd64) and adapted for parachains. use frame_support::traits::{ fungibles::{self, Balanced, CreditOf}, Contains, Currency, Get, Imbalance, OnUnbalanced, }; use pallet_asset_tx_payment::HandleCredit; use sp_runtime::traits::Zero; use sp_std::marker::PhantomData; use xcm::latest::{AssetId, Fungibility::Fungible, MultiAsset, MultiLocation}; use xcm_executor::traits::FilterAssetLocation; /// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type. pub type NegativeImbalance = as Currency< ::AccountId, >>::NegativeImbalance; /// Type alias to conveniently refer to `frame_system`'s `Config::AccountId`. pub type AccountIdOf = ::AccountId; /// Implementation of `OnUnbalanced` that deposits the fees into a staking pot for later payout. pub struct ToStakingPot(PhantomData); impl OnUnbalanced> for ToStakingPot where R: pallet_balances::Config + pallet_collator_selection::Config, AccountIdOf: From + Into, ::RuntimeEvent: From>, { fn on_nonzero_unbalanced(amount: NegativeImbalance) { let staking_pot = >::account_id(); >::resolve_creating(&staking_pot, amount); } } /// Implementation of `OnUnbalanced` that deals with the fees by combining tip and fee and passing /// the result on to `ToStakingPot`. pub struct DealWithFees(PhantomData); impl OnUnbalanced> for DealWithFees where R: pallet_balances::Config + pallet_collator_selection::Config, AccountIdOf: From + Into, ::RuntimeEvent: From>, { fn on_unbalanceds(mut fees_then_tips: impl Iterator>) { if let Some(mut fees) = fees_then_tips.next() { if let Some(tips) = fees_then_tips.next() { tips.merge_into(&mut fees); } as OnUnbalanced<_>>::on_unbalanced(fees); } } } /// A `HandleCredit` implementation that naively transfers the fees to the block author. /// Will drop and burn the assets in case the transfer fails. pub struct AssetsToBlockAuthor(PhantomData); impl HandleCredit, pallet_assets::Pallet> for AssetsToBlockAuthor where R: pallet_authorship::Config + pallet_assets::Config, AccountIdOf: From + Into, { fn handle_credit(credit: CreditOf, pallet_assets::Pallet>) { if let Some(author) = pallet_authorship::Pallet::::author() { // In case of error: Will drop the result triggering the `OnDrop` of the imbalance. let _ = pallet_assets::Pallet::::resolve(&author, credit); } } } /// Allow checking in assets that have issuance > 0. pub struct NonZeroIssuance(PhantomData<(AccountId, Assets)>); impl Contains<>::AssetId> for NonZeroIssuance where Assets: fungibles::Inspect, { fn contains(id: &>::AssetId) -> bool { !Assets::total_issuance(*id).is_zero() } } /// Asset filter that allows all assets from a certain location. pub struct AssetsFrom(PhantomData); impl> FilterAssetLocation for AssetsFrom { fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { let loc = T::get(); &loc == origin && matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) } if asset_loc.match_and_split(&loc).is_some()) } } #[cfg(test)] mod tests { use super::*; use frame_support::{ parameter_types, traits::{FindAuthor, ValidatorRegistration}, PalletId, }; use frame_system::{limits, EnsureRoot}; use pallet_collator_selection::IdentityCollator; use polkadot_primitives::v2::AccountId; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, Perbill, }; use xcm::prelude::*; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; const TEST_ACCOUNT: AccountId = AccountId::new([1; 32]); frame_support::construct_runtime!( pub enum Test where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, CollatorSelection: pallet_collator_selection::{Pallet, Call, Storage, Event}, } ); parameter_types! { pub const BlockHashCount: u64 = 250; pub BlockLength: limits::BlockLength = limits::BlockLength::max(2 * 1024); pub const AvailableBlockRatio: Perbill = Perbill::one(); pub const MaxReserves: u32 = 50; } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type BlockLength = BlockLength; type BlockWeights = (); type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { type Balance = u64; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = (); type AccountStore = System; type MaxLocks = (); type WeightInfo = (); type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; } pub struct OneAuthor; impl FindAuthor for OneAuthor { fn find_author<'a, I>(_: I) -> Option where I: 'a, { Some(TEST_ACCOUNT) } } pub struct IsRegistered; impl ValidatorRegistration for IsRegistered { fn is_registered(_id: &AccountId) -> bool { true } } parameter_types! { pub const PotId: PalletId = PalletId(*b"PotStake"); pub const MaxCandidates: u32 = 20; pub const MaxInvulnerables: u32 = 20; pub const MinCandidates: u32 = 1; } impl pallet_collator_selection::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type UpdateOrigin = EnsureRoot; type PotId = PotId; type MaxCandidates = MaxCandidates; type MinCandidates = MinCandidates; type MaxInvulnerables = MaxInvulnerables; type ValidatorId = ::AccountId; type ValidatorIdOf = IdentityCollator; type ValidatorRegistration = IsRegistered; type KickThreshold = (); type WeightInfo = (); } impl pallet_authorship::Config for Test { type FindAuthor = OneAuthor; type UncleGenerations = (); type FilterUncle = (); type EventHandler = (); } pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); // We use default for brevity, but you can configure as desired if needed. pallet_balances::GenesisConfig::::default() .assimilate_storage(&mut t) .unwrap(); t.into() } #[test] fn test_fees_and_tip_split() { new_test_ext().execute_with(|| { let fee = Balances::issue(10); let tip = Balances::issue(20); assert_eq!(Balances::free_balance(TEST_ACCOUNT), 0); DealWithFees::on_unbalanceds(vec![fee, tip].into_iter()); // Author gets 100% of tip and 100% of fee = 30 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 30); }); } #[test] fn assets_from_filters_correctly() { parameter_types! { pub SomeSiblingParachain: MultiLocation = MultiLocation::new(1, X1(Parachain(1234))); } let asset_location = SomeSiblingParachain::get() .clone() .pushed_with_interior(GeneralIndex(42)) .expect("multilocation will only have 2 junctions; qed"); let asset = MultiAsset { id: Concrete(asset_location), fun: 1_000_000.into() }; assert!( AssetsFrom::::filter_asset_location( &asset, &SomeSiblingParachain::get() ), "AssetsFrom should allow assets from any of its interior locations" ); } }