referrerpolicy=no-referrer-when-downgrade

parachains_common/
impls.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Auxiliary struct/enums for parachain runtimes.
17//! Taken from polkadot/runtime/common (at a21cd64) and adapted for parachains.
18
19use alloc::boxed::Box;
20use core::marker::PhantomData;
21use frame_support::traits::{
22	fungible, fungibles, tokens::imbalance::ResolveTo, Contains, ContainsPair, Currency, Defensive,
23	Get, Imbalance, OnUnbalanced, OriginTrait,
24};
25use pallet_asset_tx_payment::HandleCredit;
26use pallet_collator_selection::StakingPotAccountId;
27use sp_runtime::traits::Zero;
28use xcm::latest::{
29	Asset, AssetId, Fungibility, Fungibility::Fungible, Junction, Junctions::Here, Location,
30	Parent, WeightLimit,
31};
32use xcm_executor::traits::ConvertLocation;
33
34/// Type alias to conveniently refer to `frame_system`'s `Config::AccountId`.
35pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
36
37/// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type.
38pub type NegativeImbalance<T> = <pallet_balances::Pallet<T> as Currency<
39	<T as frame_system::Config>::AccountId,
40>>::NegativeImbalance;
41
42/// Implementation of `OnUnbalanced` that deposits the fees into a staking pot for later payout.
43#[deprecated(
44	note = "ToStakingPot is deprecated and will be removed after March 2024. Please use frame_support::traits::tokens::imbalance::ResolveTo instead."
45)]
46pub struct ToStakingPot<R>(PhantomData<R>);
47#[allow(deprecated)]
48impl<R> OnUnbalanced<NegativeImbalance<R>> for ToStakingPot<R>
49where
50	R: pallet_balances::Config + pallet_collator_selection::Config,
51	AccountIdOf<R>: From<polkadot_primitives::AccountId> + Into<polkadot_primitives::AccountId>,
52	<R as frame_system::Config>::RuntimeEvent: From<pallet_balances::Event<R>>,
53{
54	fn on_nonzero_unbalanced(amount: NegativeImbalance<R>) {
55		let staking_pot = <pallet_collator_selection::Pallet<R>>::account_id();
56		// In case of error: Will drop the result triggering the `OnDrop` of the imbalance.
57		<pallet_balances::Pallet<R>>::resolve_creating(&staking_pot, amount);
58	}
59}
60
61/// Fungible implementation of `OnUnbalanced` that deals with the fees by combining tip and fee and
62/// passing the result on to `ToStakingPot`.
63pub struct DealWithFees<R>(PhantomData<R>);
64impl<R> OnUnbalanced<fungible::Credit<R::AccountId, pallet_balances::Pallet<R>>> for DealWithFees<R>
65where
66	R: pallet_balances::Config + pallet_collator_selection::Config,
67	AccountIdOf<R>: From<polkadot_primitives::AccountId> + Into<polkadot_primitives::AccountId>,
68	<R as frame_system::Config>::RuntimeEvent: From<pallet_balances::Event<R>>,
69{
70	fn on_unbalanceds(
71		mut fees_then_tips: impl Iterator<
72			Item = fungible::Credit<R::AccountId, pallet_balances::Pallet<R>>,
73		>,
74	) {
75		if let Some(mut fees) = fees_then_tips.next() {
76			if let Some(tips) = fees_then_tips.next() {
77				tips.merge_into(&mut fees);
78			}
79			ResolveTo::<StakingPotAccountId<R>, pallet_balances::Pallet<R>>::on_unbalanced(fees)
80		}
81	}
82}
83
84/// A `HandleCredit` implementation that naively transfers the fees to the block author.
85/// Will drop and burn the assets in case the transfer fails.
86pub struct AssetsToBlockAuthor<R, I>(PhantomData<(R, I)>);
87impl<R, I> HandleCredit<AccountIdOf<R>, pallet_assets::Pallet<R, I>> for AssetsToBlockAuthor<R, I>
88where
89	I: 'static,
90	R: pallet_authorship::Config + pallet_assets::Config<I>,
91	AccountIdOf<R>: From<polkadot_primitives::AccountId> + Into<polkadot_primitives::AccountId>,
92{
93	fn handle_credit(credit: fungibles::Credit<AccountIdOf<R>, pallet_assets::Pallet<R, I>>) {
94		use frame_support::traits::fungibles::Balanced;
95		if let Some(author) = pallet_authorship::Pallet::<R>::author() {
96			// In case of error: Will drop the result triggering the `OnDrop` of the imbalance.
97			let _ = pallet_assets::Pallet::<R, I>::resolve(&author, credit).defensive();
98		}
99	}
100}
101
102/// Allow checking in assets that have issuance > 0.
103pub struct NonZeroIssuance<AccountId, Assets>(PhantomData<(AccountId, Assets)>);
104impl<AccountId, Assets> Contains<<Assets as fungibles::Inspect<AccountId>>::AssetId>
105	for NonZeroIssuance<AccountId, Assets>
106where
107	Assets: fungibles::Inspect<AccountId>,
108{
109	fn contains(id: &<Assets as fungibles::Inspect<AccountId>>::AssetId) -> bool {
110		!Assets::total_issuance(id.clone()).is_zero()
111	}
112}
113
114/// Allow checking in assets that exists.
115pub struct AssetExists<AccountId, Assets>(PhantomData<(AccountId, Assets)>);
116impl<AccountId, Assets> Contains<<Assets as fungibles::Inspect<AccountId>>::AssetId>
117	for AssetExists<AccountId, Assets>
118where
119	Assets: fungibles::Inspect<AccountId>,
120{
121	fn contains(id: &<Assets as fungibles::Inspect<AccountId>>::AssetId) -> bool {
122		Assets::asset_exists(id.clone())
123	}
124}
125
126/// Asset filter that allows all assets from a certain location.
127pub struct AssetsFrom<T>(PhantomData<T>);
128impl<T: Get<Location>> ContainsPair<Asset, Location> for AssetsFrom<T> {
129	fn contains(asset: &Asset, origin: &Location) -> bool {
130		let loc = T::get();
131		&loc == origin &&
132			matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) }
133			if asset_loc.match_and_split(&loc).is_some())
134	}
135}
136
137/// Type alias to conveniently refer to the `Currency::Balance` associated type.
138pub type BalanceOf<T> =
139	<pallet_balances::Pallet<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
140
141/// Implements `OnUnbalanced::on_unbalanced` to teleport slashed assets to relay chain treasury
142/// account.
143pub struct ToParentTreasury<TreasuryAccount, AccountIdConverter, T>(
144	PhantomData<(TreasuryAccount, AccountIdConverter, T)>,
145);
146
147impl<TreasuryAccount, AccountIdConverter, T> OnUnbalanced<NegativeImbalance<T>>
148	for ToParentTreasury<TreasuryAccount, AccountIdConverter, T>
149where
150	T: pallet_balances::Config + pallet_xcm::Config + frame_system::Config,
151	<<T as frame_system::Config>::RuntimeOrigin as OriginTrait>::AccountId: From<AccountIdOf<T>>,
152	[u8; 32]: From<<T as frame_system::Config>::AccountId>,
153	TreasuryAccount: Get<AccountIdOf<T>>,
154	AccountIdConverter: ConvertLocation<AccountIdOf<T>>,
155	BalanceOf<T>: Into<Fungibility>,
156{
157	fn on_unbalanced(amount: NegativeImbalance<T>) {
158		let amount = match amount.drop_zero() {
159			Ok(..) => return,
160			Err(amount) => amount,
161		};
162		let imbalance = amount.peek();
163		let root_location: Location = Here.into();
164		let root_account: AccountIdOf<T> =
165			match AccountIdConverter::convert_location(&root_location) {
166				Some(a) => a,
167				None => {
168					tracing::warn!(target: "xcm::on_unbalanced", "Failed to convert root origin into account id");
169					return
170				},
171			};
172		let treasury_account: AccountIdOf<T> = TreasuryAccount::get();
173
174		<pallet_balances::Pallet<T>>::resolve_creating(&root_account, amount);
175
176		let result = <pallet_xcm::Pallet<T>>::limited_teleport_assets(
177			<<T as frame_system::Config>::RuntimeOrigin>::root(),
178			Box::new(Parent.into()),
179			Box::new(
180				Junction::AccountId32 { network: None, id: treasury_account.into() }
181					.into_location()
182					.into(),
183			),
184			Box::new((Parent, imbalance).into()),
185			0,
186			WeightLimit::Unlimited,
187		);
188
189		if let Err(err) = result {
190			tracing::warn!(target: "xcm::on_unbalanced", error=?err, "Failed to teleport slashed assets");
191		}
192	}
193}
194
195#[cfg(test)]
196mod tests {
197	use super::*;
198	use frame_support::{
199		derive_impl, parameter_types,
200		traits::{ConstU32, FindAuthor, ValidatorRegistration},
201		PalletId,
202	};
203	use frame_system::{limits, EnsureRoot};
204	use pallet_collator_selection::IdentityCollator;
205	use polkadot_primitives::AccountId;
206	use sp_core::H256;
207	use sp_runtime::{
208		traits::{BlakeTwo256, IdentityLookup},
209		BuildStorage, Perbill,
210	};
211	use xcm::prelude::*;
212
213	type Block = frame_system::mocking::MockBlock<Test>;
214	const TEST_ACCOUNT: AccountId = AccountId::new([1; 32]);
215
216	frame_support::construct_runtime!(
217		pub enum Test
218		{
219			System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
220			Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
221			CollatorSelection: pallet_collator_selection::{Pallet, Call, Storage, Event<T>},
222		}
223	);
224
225	parameter_types! {
226		pub BlockLength: limits::BlockLength = limits::BlockLength::max(2 * 1024);
227		pub const AvailableBlockRatio: Perbill = Perbill::one();
228	}
229
230	#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
231	impl frame_system::Config for Test {
232		type BaseCallFilter = frame_support::traits::Everything;
233		type RuntimeOrigin = RuntimeOrigin;
234		type Nonce = u64;
235		type RuntimeCall = RuntimeCall;
236		type Hash = H256;
237		type Hashing = BlakeTwo256;
238		type AccountId = AccountId;
239		type Lookup = IdentityLookup<Self::AccountId>;
240		type Block = Block;
241		type RuntimeEvent = RuntimeEvent;
242		type BlockLength = BlockLength;
243		type BlockWeights = ();
244		type DbWeight = ();
245		type Version = ();
246		type PalletInfo = PalletInfo;
247		type AccountData = pallet_balances::AccountData<u64>;
248		type OnNewAccount = ();
249		type OnKilledAccount = ();
250		type SystemWeightInfo = ();
251		type SS58Prefix = ();
252		type OnSetCode = ();
253		type MaxConsumers = frame_support::traits::ConstU32<16>;
254	}
255
256	#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
257	impl pallet_balances::Config for Test {
258		type AccountStore = System;
259	}
260
261	pub struct OneAuthor;
262	impl FindAuthor<AccountId> for OneAuthor {
263		fn find_author<'a, I>(_: I) -> Option<AccountId>
264		where
265			I: 'a,
266		{
267			Some(TEST_ACCOUNT)
268		}
269	}
270
271	pub struct IsRegistered;
272	impl ValidatorRegistration<AccountId> for IsRegistered {
273		fn is_registered(_id: &AccountId) -> bool {
274			true
275		}
276	}
277
278	parameter_types! {
279		pub const PotId: PalletId = PalletId(*b"PotStake");
280	}
281
282	impl pallet_collator_selection::Config for Test {
283		type RuntimeEvent = RuntimeEvent;
284		type Currency = Balances;
285		type UpdateOrigin = EnsureRoot<AccountId>;
286		type PotId = PotId;
287		type MaxCandidates = ConstU32<20>;
288		type MinEligibleCollators = ConstU32<1>;
289		type MaxInvulnerables = ConstU32<20>;
290		type ValidatorId = <Self as frame_system::Config>::AccountId;
291		type ValidatorIdOf = IdentityCollator;
292		type ValidatorRegistration = IsRegistered;
293		type KickThreshold = ();
294		type WeightInfo = ();
295	}
296
297	impl pallet_authorship::Config for Test {
298		type FindAuthor = OneAuthor;
299		type EventHandler = ();
300	}
301
302	pub fn new_test_ext() -> sp_io::TestExternalities {
303		let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
304		// We use default for brevity, but you can configure as desired if needed.
305		pallet_balances::GenesisConfig::<Test>::default()
306			.assimilate_storage(&mut t)
307			.unwrap();
308		t.into()
309	}
310
311	#[test]
312	fn test_fees_and_tip_split() {
313		new_test_ext().execute_with(|| {
314			let fee =
315				<pallet_balances::Pallet<Test> as frame_support::traits::fungible::Balanced<
316					AccountId,
317				>>::issue(10);
318			let tip =
319				<pallet_balances::Pallet<Test> as frame_support::traits::fungible::Balanced<
320					AccountId,
321				>>::issue(20);
322
323			assert_eq!(Balances::free_balance(TEST_ACCOUNT), 0);
324
325			DealWithFees::on_unbalanceds(vec![fee, tip].into_iter());
326
327			// Author gets 100% of tip and 100% of fee = 30
328			assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 30);
329		});
330	}
331
332	#[test]
333	fn assets_from_filters_correctly() {
334		parameter_types! {
335			pub SomeSiblingParachain: Location = (Parent, Parachain(1234)).into();
336		}
337
338		let asset_location = SomeSiblingParachain::get()
339			.pushed_with_interior(GeneralIndex(42))
340			.expect("location will only have 2 junctions; qed");
341		let asset = Asset { id: AssetId(asset_location), fun: 1_000_000u128.into() };
342		assert!(
343			AssetsFrom::<SomeSiblingParachain>::contains(&asset, &SomeSiblingParachain::get()),
344			"AssetsFrom should allow assets from any of its interior locations"
345		);
346	}
347}