teyrchains_common/
impls.rs

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