Skip to main content

assets_common/
matching.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::local_and_foreign_assets::ForeignAssetReserveData;
17use core::fmt::Debug;
18use cumulus_primitives_core::ParaId;
19use frame_support::{
20	pallet_prelude::Get,
21	traits::{tokens::ProvideAssetReserves, Contains, ContainsPair},
22};
23use xcm::prelude::*;
24use xcm_builder::ensure_is_remote;
25
26frame_support::parameter_types! {
27	pub LocalLocationPattern: Location = Location::new(0, Here);
28	pub ParentLocation: Location = Location::parent();
29}
30
31/// Accepts an asset if it is from the origin.
32pub struct IsForeignConcreteAsset<IsForeign>(core::marker::PhantomData<IsForeign>);
33impl<IsForeign: ContainsPair<Location, Location>> ContainsPair<Asset, Location>
34	for IsForeignConcreteAsset<IsForeign>
35{
36	fn contains(asset: &Asset, origin: &Location) -> bool {
37		let result = matches!(asset.id, AssetId(ref id) if IsForeign::contains(id, origin));
38		tracing::trace!(target: "xcm::contains", ?asset, ?origin, ?result, "IsForeignConcreteAsset");
39		result
40	}
41}
42
43/// Checks if `a` is from sibling location `b`. Checks that `Location-a` starts with
44/// `Location-b`, and that the `ParaId` of `b` is not equal to `a`.
45pub struct FromSiblingParachain<SelfParaId, L = Location>(
46	core::marker::PhantomData<(SelfParaId, L)>,
47);
48impl<SelfParaId: Get<ParaId>, L: TryFrom<Location> + TryInto<Location> + Clone + Debug>
49	ContainsPair<L, L> for FromSiblingParachain<SelfParaId, L>
50{
51	fn contains(a: &L, b: &L) -> bool {
52		tracing::trace!(target: "xcm:contains", ?a, ?b, "FromSiblingParachain");
53		// We convert locations to latest
54		let a = match ((*a).clone().try_into(), (*b).clone().try_into()) {
55			(Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least
56			_ => return false,
57		};
58
59		// here we check if sibling
60		match a.unpack() {
61			(1, interior) => {
62				matches!(interior.first(), Some(Parachain(sibling_para_id)) if sibling_para_id.ne(&u32::from(SelfParaId::get())))
63			},
64			_ => false,
65		}
66	}
67}
68
69/// Checks if asset `a` is coming from a trusted Reserve location `b`, then checks whether the local
70/// chain is also a reserve of `a`. Assets can be teleported between their reserve locations.
71pub struct TeleportableAssetWithTrustedReserve<SelfParaId, ReserveProvider, L = Location>(
72	core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>,
73);
74impl<
75		SelfParaId: Get<ParaId>,
76		L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
77		ReserveProvider: ProvideAssetReserves<Location, ForeignAssetReserveData>,
78	> ContainsPair<L, L> for TeleportableAssetWithTrustedReserve<SelfParaId, ReserveProvider, L>
79{
80	fn contains(a: &L, b: &L) -> bool {
81		tracing::trace!(target: "xcm::contains", ?a, ?b, "TeleportableAssetWithTrustedReserve");
82		// We convert locations to latest
83		let (a, b) = match ((*a).clone().try_into(), (*b).clone().try_into()) {
84			(Ok(a), Ok(b)) => (a, b),
85			_ => return false,
86		};
87		let reserves = ReserveProvider::reserves(&a);
88		tracing::trace!(target: "xcm::contains", ?reserves, "TeleportableAssetWithTrustedReserve");
89		// check if `b` is reserve for `a` and teleportable flag is set
90		let filter = (b, true).into();
91		reserves.contains(&filter)
92	}
93}
94
95/// Checks if asset `a` is coming from a trusted Reserve location `b`.
96/// Then checks that the local chain is NOT itself also reserve of `a`, otherwise a teleport is in
97/// order.
98pub struct NonTeleportableAssetFromTrustedReserve<SelfParaId, ReserveProvider, L = Location>(
99	core::marker::PhantomData<(SelfParaId, ReserveProvider, L)>,
100);
101impl<
102		SelfParaId: Get<ParaId>,
103		L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
104		ReserveProvider: ProvideAssetReserves<Location, ForeignAssetReserveData>,
105	> ContainsPair<L, L> for NonTeleportableAssetFromTrustedReserve<SelfParaId, ReserveProvider, L>
106{
107	fn contains(a: &L, b: &L) -> bool {
108		tracing::trace!(target: "xcm::contains", ?a, ?b, "NonTeleportableAssetFromTrustedReserve");
109		// We convert locations to latest
110		let (a, b) = match ((*a).clone().try_into(), (*b).clone().try_into()) {
111			(Ok(a), Ok(b)) => (a, b),
112			_ => return false,
113		};
114		let reserves = ReserveProvider::reserves(&a);
115		tracing::trace!(target: "xcm::contains", ?reserves, "NonTeleportableAssetFromTrustedReserve");
116		// check if `b` is reserve for `a` and teleportable flag is NOT set
117		let filter = (b, false).into();
118		reserves.contains(&filter)
119	}
120}
121
122/// Checks if `a` is from the expected global consensus network. Checks that `Location-a`
123/// starts with `Location-b`, and that network is a foreign consensus system.
124pub struct FromNetwork<UniversalLocation, ExpectedNetworkId, L = Location>(
125	core::marker::PhantomData<(UniversalLocation, ExpectedNetworkId, L)>,
126);
127impl<
128		UniversalLocation: Get<InteriorLocation>,
129		ExpectedNetworkId: Get<NetworkId>,
130		L: TryFrom<Location> + TryInto<Location> + Clone + Debug,
131	> ContainsPair<L, L> for FromNetwork<UniversalLocation, ExpectedNetworkId, L>
132{
133	fn contains(a: &L, b: &L) -> bool {
134		tracing::trace!(target: "xcm:contains", ?a, ?b, "FromNetwork");
135		// We convert locations to latest
136		let a = match ((*a).clone().try_into(), (*b).clone().try_into()) {
137			(Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least
138			_ => return false,
139		};
140
141		let universal_source = UniversalLocation::get();
142
143		// ensure that `a` is remote and from the expected network
144		match ensure_is_remote(universal_source.clone(), a.clone()) {
145			Ok((network_id, _)) => network_id == ExpectedNetworkId::get(),
146			Err(e) => {
147				tracing::debug!(target: "xcm::contains", origin = ?a, ?universal_source, error = ?e, "FromNetwork origin is not remote to the universal_source");
148				false
149			},
150		}
151	}
152}
153
154/// Accept an asset if it is native to `AssetsAllowedNetworks` and it is coming from
155/// `OriginLocation`.
156pub struct RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>(
157	core::marker::PhantomData<(AssetsAllowedNetworks, OriginLocation)>,
158);
159impl<
160		L: TryInto<Location> + Clone,
161		AssetsAllowedNetworks: Contains<Location>,
162		OriginLocation: Get<Location>,
163	> ContainsPair<L, L> for RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>
164{
165	fn contains(asset: &L, origin: &L) -> bool {
166		let Ok(asset) = asset.clone().try_into() else {
167			return false;
168		};
169		let Ok(origin) = origin.clone().try_into() else {
170			return false;
171		};
172		let expected_origin = OriginLocation::get();
173		// ensure `origin` is expected `OriginLocation`
174		if !expected_origin.eq(&origin) {
175			tracing::trace!(
176				target: "xcm::contains",
177				?asset,
178				?origin,
179				?expected_origin,
180				"RemoteAssetFromLocation: Asset is not from expected origin"
181			);
182			return false;
183		} else {
184			tracing::trace!(
185				target: "xcm::contains",
186				?asset,
187				?origin,
188				"RemoteAssetFromLocation",
189			);
190		}
191
192		// ensure `asset` is from remote consensus listed in `AssetsAllowedNetworks`
193		AssetsAllowedNetworks::contains(&asset)
194	}
195}
196impl<AssetsAllowedNetworks: Contains<Location>, OriginLocation: Get<Location>>
197	ContainsPair<Asset, Location> for RemoteAssetFromLocation<AssetsAllowedNetworks, OriginLocation>
198{
199	fn contains(asset: &Asset, origin: &Location) -> bool {
200		tracing::trace!(target: "xcm:contains", ?asset, ?origin, "RemoteAssetFromLocation");
201		<Self as ContainsPair<Location, Location>>::contains(&asset.id.0, origin)
202	}
203}
204
205#[cfg(test)]
206mod tests {
207	use super::*;
208	use frame_support::parameter_types;
209	use xcm::latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH};
210
211	parameter_types! {
212		pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), Parachain(1000)].into();
213		pub ExpectedNetworkId: NetworkId = ByGenesis(WESTEND_GENESIS_HASH);
214	}
215
216	#[test]
217	fn from_network_contains_works() {
218		// asset and origin from foreign consensus works
219		let asset: Location = (
220			Parent,
221			Parent,
222			GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
223			Parachain(1000),
224			PalletInstance(1),
225			GeneralIndex(1),
226		)
227			.into();
228		let origin: Location =
229			(Parent, Parent, GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000))
230				.into();
231		assert!(FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
232
233		// asset and origin from local consensus fails
234		let asset: Location = (
235			Parent,
236			Parent,
237			GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)),
238			Parachain(1000),
239			PalletInstance(1),
240			GeneralIndex(1),
241		)
242			.into();
243		let origin: Location =
244			(Parent, Parent, GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), Parachain(1000))
245				.into();
246		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
247
248		// asset and origin from here fails
249		let asset: Location = (PalletInstance(1), GeneralIndex(1)).into();
250		let origin: Location = Here.into();
251		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
252
253		// asset from different consensus fails
254		let asset: Location = (
255			Parent,
256			Parent,
257			GlobalConsensus(Polkadot),
258			Parachain(1000),
259			PalletInstance(1),
260			GeneralIndex(1),
261		)
262			.into();
263		let origin: Location =
264			(Parent, Parent, GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000))
265				.into();
266		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
267
268		// origin from different consensus fails
269		let asset: Location = (
270			Parent,
271			Parent,
272			GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),
273			Parachain(1000),
274			PalletInstance(1),
275			GeneralIndex(1),
276		)
277			.into();
278		let origin: Location = (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into();
279		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
280
281		// asset and origin from unexpected consensus fails
282		let asset: Location = (
283			Parent,
284			Parent,
285			GlobalConsensus(Polkadot),
286			Parachain(1000),
287			PalletInstance(1),
288			GeneralIndex(1),
289		)
290			.into();
291		let origin: Location = (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into();
292		assert!(!FromNetwork::<UniversalLocation, ExpectedNetworkId>::contains(&asset, &origin));
293	}
294}