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