referrerpolicy=no-referrer-when-downgrade

assets_common/
lib.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#![cfg_attr(not(feature = "std"), no_std)]
17
18#[cfg(feature = "runtime-benchmarks")]
19pub mod benchmarks;
20mod erc20_transactor;
21pub mod foreign_creators;
22pub mod fungible_conversion;
23pub mod local_and_foreign_assets;
24pub mod matching;
25pub mod runtime_api;
26pub use erc20_transactor::ERC20Transactor;
27
28extern crate alloc;
29extern crate core;
30
31use crate::matching::{LocalLocationPattern, ParentLocation};
32use alloc::vec::Vec;
33use codec::{Decode, EncodeLike};
34use core::{cmp::PartialEq, marker::PhantomData};
35use frame_support::traits::{Contains, Equals, EverythingBut};
36use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId};
37use sp_core::H160;
38use sp_runtime::traits::{MaybeEquivalence, TryConvertInto};
39use xcm::prelude::*;
40use xcm_builder::{
41	AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter,
42};
43use xcm_executor::traits::JustTry;
44
45/// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets`
46pub type AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, L = Location> =
47	AsPrefixedGeneralIndex<
48		TrustBackedAssetsPalletLocation,
49		AssetIdForTrustBackedAssets,
50		TryConvertInto,
51		L,
52	>;
53
54/// `Location` vs `CollectionId` converter for `Uniques`
55pub type CollectionIdForUniquesConvert<UniquesPalletLocation> =
56	AsPrefixedGeneralIndex<UniquesPalletLocation, CollectionId, TryConvertInto>;
57
58/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`
59pub type TrustBackedAssetsConvertedConcreteId<
60	TrustBackedAssetsPalletLocation,
61	Balance,
62	L = Location,
63> = MatchedConvertedConcreteId<
64	AssetIdForTrustBackedAssets,
65	Balance,
66	StartsWith<TrustBackedAssetsPalletLocation>,
67	AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, L>,
68	TryConvertInto,
69>;
70
71/// [`MatchedConvertedConcreteId`] converter dedicated for `Uniques`
72pub type UniquesConvertedConcreteId<UniquesPalletLocation> = MatchedConvertedConcreteId<
73	CollectionId,
74	ItemId,
75	// The asset starts with the uniques pallet. The `CollectionId` of the asset is specified as a
76	// junction within the pallet itself.
77	StartsWith<UniquesPalletLocation>,
78	CollectionIdForUniquesConvert<UniquesPalletLocation>,
79	TryConvertInto,
80>;
81
82/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`,
83/// it is a similar implementation to `TrustBackedAssetsConvertedConcreteId`,
84/// but it converts `AssetId` to `xcm::v*::Location` type instead of `AssetIdForTrustBackedAssets =
85/// u32`
86pub type TrustBackedAssetsAsLocation<
87	TrustBackedAssetsPalletLocation,
88	Balance,
89	L,
90	LocationConverter = WithLatestLocationConverter<L>,
91> = MatchedConvertedConcreteId<
92	L,
93	Balance,
94	StartsWith<TrustBackedAssetsPalletLocation>,
95	LocationConverter,
96	TryConvertInto,
97>;
98
99/// [`MatchedConvertedConcreteId`] converter dedicated for storing `ForeignAssets` with `AssetId` as
100/// `Location`.
101///
102/// Excludes by default:
103/// - parent as relay chain
104/// - all local Locations
105///
106/// `AdditionalLocationExclusionFilter` can customize additional excluded Locations
107pub type ForeignAssetsConvertedConcreteId<
108	AdditionalLocationExclusionFilter,
109	Balance,
110	AssetId,
111	LocationToAssetIdConverter = WithLatestLocationConverter<AssetId>,
112	BalanceConverter = TryConvertInto,
113> = MatchedConvertedConcreteId<
114	AssetId,
115	Balance,
116	EverythingBut<(
117		// Excludes relay/parent chain currency
118		Equals<ParentLocation>,
119		// Here we rely on fact that something like this works:
120		// assert!(Location::new(1,
121		// [Parachain(100)]).starts_with(&Location::parent()));
122		// assert!([Parachain(100)].into().starts_with(&Here));
123		StartsWith<LocalLocationPattern>,
124		// Here we can exclude more stuff or leave it as `()`
125		AdditionalLocationExclusionFilter,
126	)>,
127	LocationToAssetIdConverter,
128	BalanceConverter,
129>;
130
131/// `Contains<Location>` implementation that matches locations with no parents,
132/// a `PalletInstance` and an `AccountKey20` junction.
133pub struct IsLocalAccountKey20;
134impl Contains<Location> for IsLocalAccountKey20 {
135	fn contains(location: &Location) -> bool {
136		matches!(location.unpack(), (0, [AccountKey20 { .. }]))
137	}
138}
139
140/// Fallible converter from a location to a `H160` that matches any location ending with
141/// an `AccountKey20` junction.
142pub struct AccountKey20ToH160;
143impl MaybeEquivalence<Location, H160> for AccountKey20ToH160 {
144	fn convert(location: &Location) -> Option<H160> {
145		match location.unpack() {
146			(0, [AccountKey20 { key, .. }]) => Some((*key).into()),
147			_ => None,
148		}
149	}
150
151	fn convert_back(key: &H160) -> Option<Location> {
152		Some(Location::new(0, [AccountKey20 { key: (*key).into(), network: None }]))
153	}
154}
155
156/// [`xcm_executor::traits::MatchesFungibles`] implementation that matches
157/// ERC20 tokens.
158pub type ERC20Matcher =
159	MatchedConvertedConcreteId<H160, u128, IsLocalAccountKey20, AccountKey20ToH160, JustTry>;
160
161pub type AssetIdForPoolAssets = u32;
162
163/// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`.
164pub type AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, L = Location> =
165	AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, TryConvertInto, L>;
166/// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets`
167pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> =
168	MatchedConvertedConcreteId<
169		AssetIdForPoolAssets,
170		Balance,
171		StartsWith<PoolAssetsPalletLocation>,
172		AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation>,
173		TryConvertInto,
174	>;
175
176/// Adapter implementation for accessing pools (`pallet_asset_conversion`) that uses `AssetKind` as
177/// a `xcm::v*` which could be different from the `xcm::latest`.
178pub struct PoolAdapter<Runtime>(PhantomData<Runtime>);
179impl<
180		Runtime: pallet_asset_conversion::Config<PoolId = (L, L), AssetKind = L>,
181		L: TryFrom<Location> + TryInto<Location> + Clone + Decode + EncodeLike + PartialEq,
182	> PoolAdapter<Runtime>
183{
184	/// Returns a vector of all assets in a pool with `asset`.
185	///
186	/// Should only be used in runtime APIs since it iterates over the whole
187	/// `pallet_asset_conversion::Pools` map.
188	///
189	/// It takes in any version of an XCM Location but always returns the latest one.
190	/// This is to allow some margin of migrating the pools when updating the XCM version.
191	///
192	/// An error of type `()` is returned if the version conversion fails for XCM locations.
193	/// This error should be mapped by the caller to a more descriptive one.
194	pub fn get_assets_in_pool_with(asset: Location) -> Result<Vec<AssetId>, ()> {
195		// convert latest to the `L` version.
196		let asset: L = asset.try_into().map_err(|_| ())?;
197		Self::iter_assets_in_pool_with(&asset)
198			.map(|location| {
199				// convert `L` to the latest `AssetId`
200				location.try_into().map_err(|_| ()).map(AssetId)
201			})
202			.collect::<Result<Vec<_>, _>>()
203	}
204
205	/// Provides a current prices. Wrapper over
206	/// `pallet_asset_conversion::Pallet::<T>::quote_price_tokens_for_exact_tokens`.
207	///
208	/// An error of type `()` is returned if the version conversion fails for XCM locations.
209	/// This error should be mapped by the caller to a more descriptive one.
210	pub fn quote_price_tokens_for_exact_tokens(
211		asset_1: Location,
212		asset_2: Location,
213		amount: Runtime::Balance,
214		include_fees: bool,
215	) -> Result<Option<Runtime::Balance>, ()> {
216		// Convert latest to the `L` version.
217		let asset_1: L = asset_1.try_into().map_err(|_| ())?;
218		let asset_2: L = asset_2.try_into().map_err(|_| ())?;
219
220		// Quote swap price.
221		Ok(pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens(
222			asset_1,
223			asset_2,
224			amount,
225			include_fees,
226		))
227	}
228
229	/// Helper function for filtering pool.
230	pub fn iter_assets_in_pool_with(asset: &L) -> impl Iterator<Item = L> + '_ {
231		pallet_asset_conversion::Pools::<Runtime>::iter_keys().filter_map(|(asset_1, asset_2)| {
232			if asset_1 == *asset {
233				Some(asset_2)
234			} else if asset_2 == *asset {
235				Some(asset_1)
236			} else {
237				None
238			}
239		})
240	}
241}
242
243#[cfg(test)]
244mod tests {
245	use super::*;
246	use sp_runtime::traits::MaybeEquivalence;
247	use xcm_builder::{StartsWithExplicitGlobalConsensus, WithLatestLocationConverter};
248	use xcm_executor::traits::{Error as MatchError, MatchesFungibles};
249
250	#[test]
251	fn asset_id_for_trust_backed_assets_convert_works() {
252		frame_support::parameter_types! {
253			pub TrustBackedAssetsPalletLocation: Location = Location::new(5, [PalletInstance(13)]);
254		}
255		let local_asset_id = 123456789 as AssetIdForTrustBackedAssets;
256		let expected_reverse_ref =
257			Location::new(5, [PalletInstance(13), GeneralIndex(local_asset_id.into())]);
258
259		assert_eq!(
260			AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert_back(
261				&local_asset_id
262			)
263			.unwrap(),
264			expected_reverse_ref
265		);
266		assert_eq!(
267			AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert(
268				&expected_reverse_ref
269			)
270			.unwrap(),
271			local_asset_id
272		);
273	}
274
275	#[test]
276	fn trust_backed_assets_match_fungibles_works() {
277		frame_support::parameter_types! {
278			pub TrustBackedAssetsPalletLocation: Location = Location::new(0, [PalletInstance(13)]);
279		}
280		// set up a converter
281		type TrustBackedAssetsConvert =
282			TrustBackedAssetsConvertedConcreteId<TrustBackedAssetsPalletLocation, u128>;
283
284		let test_data = vec![
285			// missing GeneralIndex
286			(ma_1000(0, [PalletInstance(13)].into()), Err(MatchError::AssetIdConversionFailed)),
287			(
288				ma_1000(0, [PalletInstance(13), GeneralKey { data: [0; 32], length: 32 }].into()),
289				Err(MatchError::AssetIdConversionFailed),
290			),
291			(
292				ma_1000(0, [PalletInstance(13), Parachain(1000)].into()),
293				Err(MatchError::AssetIdConversionFailed),
294			),
295			// OK
296			(ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()), Ok((1234, 1000))),
297			(
298				ma_1000(0, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
299				Ok((1234, 1000)),
300			),
301			(
302				ma_1000(
303					0,
304					[
305						PalletInstance(13),
306						GeneralIndex(1234),
307						GeneralIndex(2222),
308						GeneralKey { data: [0; 32], length: 32 },
309					]
310					.into(),
311				),
312				Ok((1234, 1000)),
313			),
314			// wrong pallet instance
315			(
316				ma_1000(0, [PalletInstance(77), GeneralIndex(1234)].into()),
317				Err(MatchError::AssetNotHandled),
318			),
319			(
320				ma_1000(0, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()),
321				Err(MatchError::AssetNotHandled),
322			),
323			// wrong parent
324			(
325				ma_1000(1, [PalletInstance(13), GeneralIndex(1234)].into()),
326				Err(MatchError::AssetNotHandled),
327			),
328			(
329				ma_1000(1, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
330				Err(MatchError::AssetNotHandled),
331			),
332			(
333				ma_1000(1, [PalletInstance(77), GeneralIndex(1234)].into()),
334				Err(MatchError::AssetNotHandled),
335			),
336			(
337				ma_1000(1, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()),
338				Err(MatchError::AssetNotHandled),
339			),
340			// wrong parent
341			(
342				ma_1000(2, [PalletInstance(13), GeneralIndex(1234)].into()),
343				Err(MatchError::AssetNotHandled),
344			),
345			(
346				ma_1000(2, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
347				Err(MatchError::AssetNotHandled),
348			),
349			// missing GeneralIndex
350			(ma_1000(0, [PalletInstance(77)].into()), Err(MatchError::AssetNotHandled)),
351			(ma_1000(1, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)),
352			(ma_1000(2, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)),
353		];
354
355		for (asset, expected_result) in test_data {
356			assert_eq!(
357				<TrustBackedAssetsConvert as MatchesFungibles<AssetIdForTrustBackedAssets, u128>>::matches_fungibles(&asset.clone().try_into().unwrap()),
358				expected_result, "asset: {:?}", asset);
359		}
360	}
361
362	#[test]
363	fn foreign_assets_converted_concrete_id_converter_works() {
364		frame_support::parameter_types! {
365			pub Parachain100Pattern: Location = Location::new(1, [Parachain(100)]);
366			pub UniversalLocationNetworkId: NetworkId = NetworkId::ByGenesis([9; 32]);
367		}
368
369		// set up a converter which uses `xcm::v4::Location` under the hood
370		type Convert = ForeignAssetsConvertedConcreteId<
371			(
372				StartsWith<Parachain100Pattern>,
373				StartsWithExplicitGlobalConsensus<UniversalLocationNetworkId>,
374			),
375			u128,
376			xcm::v4::Location,
377			WithLatestLocationConverter<xcm::v4::Location>,
378		>;
379
380		let test_data = vec![
381			// excluded as local
382			(ma_1000(0, Here), Err(MatchError::AssetNotHandled)),
383			(ma_1000(0, [Parachain(100)].into()), Err(MatchError::AssetNotHandled)),
384			(
385				ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()),
386				Err(MatchError::AssetNotHandled),
387			),
388			// excluded as parent
389			(ma_1000(1, Here), Err(MatchError::AssetNotHandled)),
390			// excluded as additional filter - Parachain100Pattern
391			(ma_1000(1, [Parachain(100)].into()), Err(MatchError::AssetNotHandled)),
392			(
393				ma_1000(1, [Parachain(100), GeneralIndex(1234)].into()),
394				Err(MatchError::AssetNotHandled),
395			),
396			(
397				ma_1000(1, [Parachain(100), PalletInstance(13), GeneralIndex(1234)].into()),
398				Err(MatchError::AssetNotHandled),
399			),
400			// excluded as additional filter - StartsWithExplicitGlobalConsensus
401			(
402				ma_1000(1, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()),
403				Err(MatchError::AssetNotHandled),
404			),
405			(
406				ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()),
407				Err(MatchError::AssetNotHandled),
408			),
409			(
410				ma_1000(
411					2,
412					[
413						GlobalConsensus(NetworkId::ByGenesis([9; 32])),
414						Parachain(200),
415						GeneralIndex(1234),
416					]
417					.into(),
418				),
419				Err(MatchError::AssetNotHandled),
420			),
421			// ok
422			(
423				ma_1000(1, [Parachain(200)].into()),
424				Ok((xcm::v4::Location::new(1, [xcm::v4::Junction::Parachain(200)]), 1000)),
425			),
426			(
427				ma_1000(2, [Parachain(200)].into()),
428				Ok((xcm::v4::Location::new(2, [xcm::v4::Junction::Parachain(200)]), 1000)),
429			),
430			(
431				ma_1000(1, [Parachain(200), GeneralIndex(1234)].into()),
432				Ok((
433					xcm::v4::Location::new(
434						1,
435						[xcm::v4::Junction::Parachain(200), xcm::v4::Junction::GeneralIndex(1234)],
436					),
437					1000,
438				)),
439			),
440			(
441				ma_1000(2, [Parachain(200), GeneralIndex(1234)].into()),
442				Ok((
443					xcm::v4::Location::new(
444						2,
445						[xcm::v4::Junction::Parachain(200), xcm::v4::Junction::GeneralIndex(1234)],
446					),
447					1000,
448				)),
449			),
450			(
451				ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([7; 32]))].into()),
452				Ok((
453					xcm::v4::Location::new(
454						2,
455						[xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis(
456							[7; 32],
457						))],
458					),
459					1000,
460				)),
461			),
462			(
463				ma_1000(
464					2,
465					[
466						GlobalConsensus(NetworkId::ByGenesis([7; 32])),
467						Parachain(200),
468						GeneralIndex(1234),
469					]
470					.into(),
471				),
472				Ok((
473					xcm::v4::Location::new(
474						2,
475						[
476							xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis(
477								[7; 32],
478							)),
479							xcm::v4::Junction::Parachain(200),
480							xcm::v4::Junction::GeneralIndex(1234),
481						],
482					),
483					1000,
484				)),
485			),
486		];
487
488		for (asset, expected_result) in test_data {
489			assert_eq!(
490				<Convert as MatchesFungibles<xcm::v4::Location, u128>>::matches_fungibles(
491					&asset.clone().try_into().unwrap()
492				),
493				expected_result,
494				"asset: {:?}",
495				asset
496			);
497		}
498	}
499
500	// Create Asset
501	fn ma_1000(parents: u8, interior: Junctions) -> Asset {
502		(Location::new(parents, interior), 1000).into()
503	}
504}