referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
asset_conversion.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Adapters to work with [`frame_support::traits::fungibles`] through XCM.
18
19use core::{marker::PhantomData, result};
20use frame_support::traits::{Contains, Get};
21use sp_runtime::traits::MaybeEquivalence;
22use xcm::latest::prelude::*;
23use xcm_executor::traits::{
24	Error as MatchError, MatchesFungibles, MatchesInstance, MatchesNonFungible, MatchesNonFungibles,
25};
26
27/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be
28/// `TryFrom/TryInto<u128>`) into a `GeneralIndex` junction, prefixed by some `Location` value.
29/// The `Location` value will typically be a `PalletInstance` junction.
30pub struct AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId, L = Location>(
31	PhantomData<(Prefix, AssetId, ConvertAssetId, L)>,
32);
33impl<
34		Prefix: Get<L>,
35		AssetId: Clone,
36		ConvertAssetId: MaybeEquivalence<u128, AssetId>,
37		L: TryInto<Location> + TryFrom<Location> + Clone,
38	> MaybeEquivalence<L, AssetId> for AsPrefixedGeneralIndex<Prefix, AssetId, ConvertAssetId, L>
39{
40	fn convert(id: &L) -> Option<AssetId> {
41		let prefix = Prefix::get();
42		let latest_prefix: Location = prefix.try_into().ok()?;
43		let latest_id: Location = (*id).clone().try_into().ok()?;
44		if latest_prefix.parent_count() != latest_id.parent_count() ||
45			latest_prefix
46				.interior()
47				.iter()
48				.enumerate()
49				.any(|(index, junction)| latest_id.interior().at(index) != Some(junction))
50		{
51			return None
52		}
53		match latest_id.interior().at(latest_prefix.interior().len()) {
54			Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert(&id),
55			_ => None,
56		}
57	}
58	fn convert_back(what: &AssetId) -> Option<L> {
59		let location = Prefix::get();
60		let mut latest_location: Location = location.try_into().ok()?;
61		let id = ConvertAssetId::convert_back(what)?;
62		latest_location.push_interior(Junction::GeneralIndex(id)).ok()?;
63		latest_location.try_into().ok()
64	}
65}
66
67pub struct ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertOther>(
68	PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>,
69);
70impl<
71		AssetId: Clone,
72		Balance: Clone,
73		ConvertAssetId: MaybeEquivalence<Location, AssetId>,
74		ConvertBalance: MaybeEquivalence<u128, Balance>,
75	> MatchesFungibles<AssetId, Balance>
76	for ConvertedConcreteId<AssetId, Balance, ConvertAssetId, ConvertBalance>
77{
78	fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), MatchError> {
79		let (amount, id) = match (&a.fun, &a.id) {
80			(Fungible(ref amount), AssetId(ref id)) => (amount, id),
81			_ => return Err(MatchError::AssetNotHandled),
82		};
83		let what = ConvertAssetId::convert(id).ok_or(MatchError::AssetIdConversionFailed)?;
84		let amount =
85			ConvertBalance::convert(amount).ok_or(MatchError::AmountToBalanceConversionFailed)?;
86		Ok((what, amount))
87	}
88}
89impl<
90		ClassId: Clone,
91		InstanceId: Clone,
92		ConvertClassId: MaybeEquivalence<Location, ClassId>,
93		ConvertInstanceId: MaybeEquivalence<AssetInstance, InstanceId>,
94	> MatchesNonFungibles<ClassId, InstanceId>
95	for ConvertedConcreteId<ClassId, InstanceId, ConvertClassId, ConvertInstanceId>
96{
97	fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> {
98		let (instance, class) = match (&a.fun, &a.id) {
99			(NonFungible(ref instance), AssetId(ref class)) => (instance, class),
100			_ => return Err(MatchError::AssetNotHandled),
101		};
102		let what = ConvertClassId::convert(class).ok_or(MatchError::AssetIdConversionFailed)?;
103		let instance =
104			ConvertInstanceId::convert(instance).ok_or(MatchError::InstanceConversionFailed)?;
105		Ok((what, instance))
106	}
107}
108
109#[deprecated = "Use `ConvertedConcreteId` instead"]
110pub type ConvertedConcreteAssetId<A, B, C, O> = ConvertedConcreteId<A, B, C, O>;
111
112pub struct MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther>(
113	PhantomData<(AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther)>,
114);
115impl<
116		AssetId: Clone,
117		Balance: Clone,
118		MatchAssetId: Contains<Location>,
119		ConvertAssetId: MaybeEquivalence<Location, AssetId>,
120		ConvertBalance: MaybeEquivalence<u128, Balance>,
121	> MatchesFungibles<AssetId, Balance>
122	for MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertBalance>
123{
124	fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), MatchError> {
125		let (amount, id) = match (&a.fun, &a.id) {
126			(Fungible(ref amount), AssetId(ref id)) if MatchAssetId::contains(id) => (amount, id),
127			_ => return Err(MatchError::AssetNotHandled),
128		};
129		let what = ConvertAssetId::convert(id).ok_or(MatchError::AssetIdConversionFailed)?;
130		let amount =
131			ConvertBalance::convert(amount).ok_or(MatchError::AmountToBalanceConversionFailed)?;
132		Ok((what, amount))
133	}
134}
135impl<
136		ClassId: Clone,
137		InstanceId: Clone,
138		MatchClassId: Contains<Location>,
139		ConvertClassId: MaybeEquivalence<Location, ClassId>,
140		ConvertInstanceId: MaybeEquivalence<AssetInstance, InstanceId>,
141	> MatchesNonFungibles<ClassId, InstanceId>
142	for MatchedConvertedConcreteId<
143		ClassId,
144		InstanceId,
145		MatchClassId,
146		ConvertClassId,
147		ConvertInstanceId,
148	>
149{
150	fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> {
151		let (instance, class) = match (&a.fun, &a.id) {
152			(NonFungible(ref instance), AssetId(ref class)) if MatchClassId::contains(class) =>
153				(instance, class),
154			_ => return Err(MatchError::AssetNotHandled),
155		};
156		let what = ConvertClassId::convert(class).ok_or(MatchError::AssetIdConversionFailed)?;
157		let instance =
158			ConvertInstanceId::convert(instance).ok_or(MatchError::InstanceConversionFailed)?;
159		Ok((what, instance))
160	}
161}
162
163/// An adapter that implements the unified unique instances matcher [`MatchesInstance`] trait
164/// for the [`MatchesNonFungibles`].
165/// The resulting matcher expects the instances to be part of some class (i.e., instance group,
166/// such as an NFT collection).
167///
168/// * `ClassId` is the ID of an instance class (e.g., NFT collection ID),
169/// * `InstanceId` is a class-scoped ID of a class member's unique instance (e.g., an NFT ID inside
170///   a collection).
171pub struct MatchInClassInstances<Matcher>(PhantomData<Matcher>);
172
173impl<ClassId, InstanceId, Matcher: MatchesNonFungibles<ClassId, InstanceId>>
174	MatchesInstance<(ClassId, InstanceId)> for MatchInClassInstances<Matcher>
175{
176	fn matches_instance(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> {
177		Matcher::matches_nonfungibles(a)
178	}
179}
180
181/// An adapter that implements the unified unique instances matcher [`MatchesInstance`] trait
182/// for the [`MatchesNonFungible`].
183/// The resulting matcher expects the instances to be fully individual, not belonging to any group
184/// (such as an NFT collection).
185///
186/// In practice, this typically means that the `InstanceId` is an indivisible ID (i.e., it is not
187/// composed of multiple IDs).
188pub struct MatchClasslessInstances<Matcher>(PhantomData<Matcher>);
189
190impl<InstanceId, Matcher: MatchesNonFungible<InstanceId>> MatchesInstance<InstanceId>
191	for MatchClasslessInstances<Matcher>
192{
193	fn matches_instance(a: &Asset) -> result::Result<InstanceId, MatchError> {
194		Matcher::matches_nonfungible(a).ok_or(MatchError::AssetNotHandled)
195	}
196}
197
198#[cfg(test)]
199mod tests {
200	use super::*;
201
202	use xcm_executor::traits::JustTry;
203
204	struct OnlyParentZero;
205	impl Contains<Location> for OnlyParentZero {
206		fn contains(a: &Location) -> bool {
207			match a {
208				Location { parents: 0, .. } => true,
209				_ => false,
210			}
211		}
212	}
213
214	#[test]
215	fn matched_converted_concrete_id_for_fungibles_works() {
216		type AssetIdForTrustBackedAssets = u32;
217		type Balance = u128;
218		frame_support::parameter_types! {
219			pub TrustBackedAssetsPalletLocation: Location = PalletInstance(50).into();
220		}
221
222		// ConvertedConcreteId cfg
223		type Converter = MatchedConvertedConcreteId<
224			AssetIdForTrustBackedAssets,
225			Balance,
226			OnlyParentZero,
227			AsPrefixedGeneralIndex<
228				TrustBackedAssetsPalletLocation,
229				AssetIdForTrustBackedAssets,
230				JustTry,
231			>,
232			JustTry,
233		>;
234		assert_eq!(
235			TrustBackedAssetsPalletLocation::get(),
236			Location { parents: 0, interior: [PalletInstance(50)].into() }
237		);
238
239		// err - does not match
240		assert_eq!(
241			Converter::matches_fungibles(&Asset {
242				id: AssetId(Location::new(1, [PalletInstance(50), GeneralIndex(1)])),
243				fun: Fungible(12345),
244			}),
245			Err(MatchError::AssetNotHandled)
246		);
247
248		// err - matches, but convert fails
249		assert_eq!(
250			Converter::matches_fungibles(&Asset {
251				id: AssetId(Location::new(
252					0,
253					[PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }]
254				)),
255				fun: Fungible(12345),
256			}),
257			Err(MatchError::AssetIdConversionFailed)
258		);
259
260		// err - matches, but NonFungible
261		assert_eq!(
262			Converter::matches_fungibles(&Asset {
263				id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
264				fun: NonFungible(Index(54321)),
265			}),
266			Err(MatchError::AssetNotHandled)
267		);
268
269		// ok
270		assert_eq!(
271			Converter::matches_fungibles(&Asset {
272				id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
273				fun: Fungible(12345),
274			}),
275			Ok((1, 12345))
276		);
277	}
278
279	#[test]
280	fn matched_converted_concrete_id_for_nonfungibles_works() {
281		type ClassId = u32;
282		type ClassInstanceId = u64;
283		frame_support::parameter_types! {
284			pub TrustBackedAssetsPalletLocation: Location = PalletInstance(50).into();
285		}
286
287		// ConvertedConcreteId cfg
288		struct ClassInstanceIdConverter;
289		impl MaybeEquivalence<AssetInstance, ClassInstanceId> for ClassInstanceIdConverter {
290			fn convert(value: &AssetInstance) -> Option<ClassInstanceId> {
291				(*value).try_into().ok()
292			}
293
294			fn convert_back(value: &ClassInstanceId) -> Option<AssetInstance> {
295				Some(AssetInstance::from(*value))
296			}
297		}
298
299		type Converter = MatchedConvertedConcreteId<
300			ClassId,
301			ClassInstanceId,
302			OnlyParentZero,
303			AsPrefixedGeneralIndex<TrustBackedAssetsPalletLocation, ClassId, JustTry>,
304			ClassInstanceIdConverter,
305		>;
306		assert_eq!(
307			TrustBackedAssetsPalletLocation::get(),
308			Location { parents: 0, interior: [PalletInstance(50)].into() }
309		);
310
311		// err - does not match
312		assert_eq!(
313			Converter::matches_nonfungibles(&Asset {
314				id: AssetId(Location::new(1, [PalletInstance(50), GeneralIndex(1)])),
315				fun: NonFungible(Index(54321)),
316			}),
317			Err(MatchError::AssetNotHandled)
318		);
319
320		// err - matches, but convert fails
321		assert_eq!(
322			Converter::matches_nonfungibles(&Asset {
323				id: AssetId(Location::new(
324					0,
325					[PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }]
326				)),
327				fun: NonFungible(Index(54321)),
328			}),
329			Err(MatchError::AssetIdConversionFailed)
330		);
331
332		// err - matches, but Fungible vs NonFungible
333		assert_eq!(
334			Converter::matches_nonfungibles(&Asset {
335				id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
336				fun: Fungible(12345),
337			}),
338			Err(MatchError::AssetNotHandled)
339		);
340
341		// ok
342		assert_eq!(
343			Converter::matches_nonfungibles(&Asset {
344				id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])),
345				fun: NonFungible(Index(54321)),
346			}),
347			Ok((1, 54321))
348		);
349	}
350}