referrerpolicy=no-referrer-when-downgrade

parachains_common/
xcm_config.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
16use crate::impls::AccountIdOf;
17use core::marker::PhantomData;
18use cumulus_primitives_core::{IsSystem, ParaId};
19use frame_support::{
20	traits::{fungibles::Inspect, tokens::ConversionToAssetBalance, Contains, ContainsPair},
21	weights::Weight,
22};
23use sp_runtime::traits::Get;
24use xcm::latest::prelude::*;
25
26/// A `ChargeFeeInFungibles` implementation that converts the output of
27/// a given WeightToFee implementation an amount charged in
28/// a particular assetId from pallet-assets
29pub struct AssetFeeAsExistentialDepositMultiplier<
30	Runtime,
31	WeightToFee,
32	BalanceConverter,
33	AssetInstance: 'static,
34>(PhantomData<(Runtime, WeightToFee, BalanceConverter, AssetInstance)>);
35impl<CurrencyBalance, Runtime, WeightToFee, BalanceConverter, AssetInstance>
36	cumulus_primitives_utility::ChargeWeightInFungibles<
37		AccountIdOf<Runtime>,
38		pallet_assets::Pallet<Runtime, AssetInstance>,
39	> for AssetFeeAsExistentialDepositMultiplier<Runtime, WeightToFee, BalanceConverter, AssetInstance>
40where
41	Runtime: pallet_assets::Config<AssetInstance>,
42	WeightToFee: frame_support::weights::WeightToFee<Balance = CurrencyBalance>,
43	BalanceConverter: ConversionToAssetBalance<
44		CurrencyBalance,
45		<Runtime as pallet_assets::Config<AssetInstance>>::AssetId,
46		<Runtime as pallet_assets::Config<AssetInstance>>::Balance,
47	>,
48	<BalanceConverter as ConversionToAssetBalance<
49		CurrencyBalance,
50		<Runtime as pallet_assets::Config<AssetInstance>>::AssetId,
51		<Runtime as pallet_assets::Config<AssetInstance>>::Balance,
52	>>::Error: core::fmt::Debug,
53{
54	fn charge_weight_in_fungibles(
55		asset_id: <pallet_assets::Pallet<Runtime, AssetInstance> as Inspect<
56			AccountIdOf<Runtime>,
57		>>::AssetId,
58		weight: Weight,
59	) -> Result<
60		<pallet_assets::Pallet<Runtime, AssetInstance> as Inspect<AccountIdOf<Runtime>>>::Balance,
61		XcmError,
62	> {
63		let amount = WeightToFee::weight_to_fee(&weight);
64		// If the amount gotten is not at least the ED, then make it be the ED of the asset
65		// This is to avoid burning assets and decreasing the supply
66		let asset_amount = BalanceConverter::to_asset_balance(amount, asset_id)
67			.map_err(|error| {
68				tracing::debug!(target: "xcm::charge_weight_in_fungibles", ?error, "AssetFeeAsExistentialDepositMultiplier cannot convert to valid balance (possibly below ED)");
69				XcmError::TooExpensive
70			})?;
71		Ok(asset_amount)
72	}
73}
74
75/// Accepts an asset if it is a native asset from a particular `Location`.
76pub struct ConcreteNativeAssetFrom<LocationValue>(PhantomData<LocationValue>);
77impl<LocationValue: Get<Location>> ContainsPair<Asset, Location>
78	for ConcreteNativeAssetFrom<LocationValue>
79{
80	fn contains(asset: &Asset, origin: &Location) -> bool {
81		tracing::trace!(
82			target: "xcm::filter_asset_location",
83			?asset, ?origin, location=?LocationValue::get(),
84			"ConcreteNativeAsset"
85		);
86		asset.id.0 == *origin && origin == &LocationValue::get()
87	}
88}
89
90pub struct RelayOrOtherSystemParachains<
91	SystemParachainMatcher: Contains<Location>,
92	Runtime: parachain_info::Config,
93> {
94	_runtime: PhantomData<(SystemParachainMatcher, Runtime)>,
95}
96impl<SystemParachainMatcher: Contains<Location>, Runtime: parachain_info::Config> Contains<Location>
97	for RelayOrOtherSystemParachains<SystemParachainMatcher, Runtime>
98{
99	fn contains(l: &Location) -> bool {
100		let self_para_id: u32 = parachain_info::Pallet::<Runtime>::get().into();
101		if let (0, [Parachain(para_id)]) = l.unpack() {
102			if *para_id == self_para_id {
103				return false
104			}
105		}
106		matches!(l.unpack(), (1, [])) || SystemParachainMatcher::contains(l)
107	}
108}
109
110/// Contains all sibling system parachains, including the one where this matcher is used.
111///
112/// This structure can only be used at a parachain level. In the Relay Chain, please use
113/// the `xcm_builder::IsChildSystemParachain` matcher.
114pub struct AllSiblingSystemParachains;
115impl Contains<Location> for AllSiblingSystemParachains {
116	fn contains(l: &Location) -> bool {
117		tracing::trace!(target: "xcm::contains", location=?l, "AllSiblingSystemParachains");
118		match l.unpack() {
119			// System parachain
120			(1, [Parachain(id)]) => ParaId::from(*id).is_system(),
121			// Everything else
122			_ => false,
123		}
124	}
125}
126
127/// Accepts an asset if it is a concrete asset from the system (Relay Chain or system parachain).
128pub struct ConcreteAssetFromSystem<AssetLocation>(PhantomData<AssetLocation>);
129impl<AssetLocation: Get<Location>> ContainsPair<Asset, Location>
130	for ConcreteAssetFromSystem<AssetLocation>
131{
132	fn contains(asset: &Asset, origin: &Location) -> bool {
133		tracing::trace!(target: "xcm::contains", ?asset, ?origin, "ConcreteAssetFromSystem");
134		let is_system = match origin.unpack() {
135			// The Relay Chain
136			(1, []) => true,
137			// System parachain
138			(1, [Parachain(id)]) => ParaId::from(*id).is_system(),
139			// Others
140			_ => false,
141		};
142		asset.id.0 == AssetLocation::get() && is_system
143	}
144}
145
146/// Filter to check if a given location is the parent Relay Chain or a sibling parachain.
147///
148/// This type should only be used within the context of a parachain, since it does not verify that
149/// the parent is indeed a Relay Chain.
150pub struct ParentRelayOrSiblingParachains;
151impl Contains<Location> for ParentRelayOrSiblingParachains {
152	fn contains(location: &Location) -> bool {
153		matches!(location.unpack(), (1, []) | (1, [Parachain(_)]))
154	}
155}
156
157/// Filter to check if a given `target` location represents the same AccountId32 as `origin`,
158/// but coming from another sibling system chain.
159///
160/// This type should only be used within the context of a parachain, to allow accounts on system
161/// chains to Alias to the same accounts on the local chain.
162pub struct AliasAccountId32FromSiblingSystemChain;
163impl ContainsPair<Location, Location> for AliasAccountId32FromSiblingSystemChain {
164	fn contains(origin: &Location, target: &Location) -> bool {
165		let result = match origin.unpack() {
166			// `origin` is AccountId32 on sibling system parachain
167			(1, [Parachain(para_id), AccountId32 { network: _, id: origin }])
168				if ParaId::from(*para_id).is_system() =>
169			{
170				match target.unpack() {
171					// `target` is local AccountId32 and matches `origin` remote account
172					(0, [AccountId32 { network: _, id: target }]) => target.eq(origin),
173					_ => false,
174				}
175			},
176			_ => false,
177		};
178		tracing::trace!(
179			target: "xcm::contains",
180			?origin, ?target, ?result,
181			"AliasAccountId32FromSiblingSystemChain"
182		);
183		result
184	}
185}
186
187#[cfg(test)]
188mod tests {
189	use frame_support::{parameter_types, traits::Contains};
190
191	use super::{
192		AliasAccountId32FromSiblingSystemChain, AllSiblingSystemParachains, Asset,
193		ConcreteAssetFromSystem, ContainsPair, GeneralIndex, Here, Location, PalletInstance,
194		Parachain, Parent,
195	};
196	use polkadot_primitives::LOWEST_PUBLIC_ID;
197	use xcm::latest::prelude::*;
198
199	parameter_types! {
200		pub const RelayLocation: Location = Location::parent();
201	}
202
203	#[test]
204	fn concrete_asset_from_relay_works() {
205		let expected_asset: Asset = (Parent, 1000000).into();
206		let expected_origin: Location = (Parent, Here).into();
207
208		assert!(<ConcreteAssetFromSystem<RelayLocation>>::contains(
209			&expected_asset,
210			&expected_origin
211		));
212	}
213
214	#[test]
215	fn concrete_asset_from_sibling_system_para_fails_for_wrong_asset() {
216		let unexpected_assets: Vec<Asset> = vec![
217			(Here, 1000000).into(),
218			((PalletInstance(50), GeneralIndex(1)), 1000000).into(),
219			((Parent, Parachain(1000), PalletInstance(50), GeneralIndex(1)), 1000000).into(),
220		];
221		let expected_origin: Location = (Parent, Parachain(1000)).into();
222
223		unexpected_assets.iter().for_each(|asset| {
224			assert!(!<ConcreteAssetFromSystem<RelayLocation>>::contains(asset, &expected_origin));
225		});
226	}
227
228	#[test]
229	fn concrete_asset_from_sibling_system_para_works_for_correct_asset() {
230		// (para_id, expected_result)
231		let test_data = vec![
232			(0, true),
233			(1, true),
234			(1000, true),
235			(1999, true),
236			(2000, false), // Not a System Parachain
237			(2001, false), // Not a System Parachain
238		];
239
240		let expected_asset: Asset = (Parent, 1000000).into();
241
242		for (para_id, expected_result) in test_data {
243			let origin: Location = (Parent, Parachain(para_id)).into();
244			assert_eq!(
245				expected_result,
246				<ConcreteAssetFromSystem<RelayLocation>>::contains(&expected_asset, &origin)
247			);
248		}
249	}
250
251	#[test]
252	fn all_sibling_system_parachains_works() {
253		// system parachain
254		assert!(AllSiblingSystemParachains::contains(&Location::new(1, [Parachain(1)])));
255		// non-system parachain
256		assert!(!AllSiblingSystemParachains::contains(&Location::new(
257			1,
258			[Parachain(LOWEST_PUBLIC_ID.into())]
259		)));
260		// when used at relay chain
261		assert!(!AllSiblingSystemParachains::contains(&Location::new(0, [Parachain(1)])));
262		// when used with non-parachain
263		assert!(!AllSiblingSystemParachains::contains(&Location::new(1, [OnlyChild])));
264	}
265
266	#[test]
267	fn alias_accountid32_from_sibling_system_parachains() {
268		let acc_42 = AccountId32 { network: None, id: [42u8; 32] };
269		let acc_13 = AccountId32 { network: None, id: [13u8; 32] };
270		// origin acc_42 on sibling system parachain aliases into local acc_42
271		assert!(AliasAccountId32FromSiblingSystemChain::contains(
272			&Location::new(1, [Parachain(1), acc_42]),
273			&Location::new(0, [acc_42])
274		));
275		// if target is not local account, always fails
276		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
277			&Location::new(1, [Parachain(1), acc_42]),
278			&Location::new(0, [])
279		));
280		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
281			&Location::new(1, [Parachain(1), acc_42]),
282			&Location::new(0, [Parachain(1)])
283		));
284		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
285			&Location::new(1, [Parachain(1), acc_42]),
286			&Location::new(0, [GeneralIndex(42)])
287		));
288		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
289			&Location::new(1, [Parachain(1), acc_42]),
290			&Location::new(1, [acc_42])
291		));
292		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
293			&Location::new(1, [Parachain(1), acc_42]),
294			&Location::new(2, [acc_42])
295		));
296		// origin acc_13 on sibling system parachain CANNOT alias into local acc_42
297		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
298			&Location::new(1, [Parachain(1), acc_13]),
299			&Location::new(0, [acc_42])
300		));
301		// origin acc_42 on sibling non-system parachain CANNOT alias into local acc_42
302		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
303			&Location::new(1, [Parachain(LOWEST_PUBLIC_ID.into()), acc_42]),
304			&Location::new(0, [acc_42])
305		));
306		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
307			&Location::new(0, [acc_13]),
308			&Location::new(0, [acc_13]),
309		));
310		assert!(!AliasAccountId32FromSiblingSystemChain::contains(
311			&Location::new(0, [acc_42]),
312			&Location::new(1, [Parachain(1), acc_42]),
313		));
314	}
315}