pez_assets_common/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
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 pezframe_support::traits::{Contains, Equals, EverythingBut};
38use pezsp_core::H160;
39use pezsp_runtime::traits::{MaybeEquivalence, TryConvertInto};
40use teyrchains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId};
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 pezpallet. The `CollectionId` of the asset is specified as
78	// a junction within the pezpallet 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		// [Teyrchain(100)]).starts_with(&Location::parent()));
124		// assert!([Teyrchain(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 (`pezpallet_asset_conversion`) that uses `AssetKind`
179/// as a `xcm::v*` which could be different from the `xcm::latest`.
180pub struct PoolAdapter<Runtime>(PhantomData<Runtime>);
181impl<
182		Runtime: pezpallet_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	/// `pezpallet_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	/// `pezpallet_asset_conversion::Pezpallet::<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(pezpallet_asset_conversion::Pezpallet::<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		pezpallet_asset_conversion::Pools::<Runtime>::iter_keys().filter_map(
234			|(asset_1, asset_2)| {
235				if asset_1 == *asset {
236					Some(asset_2)
237				} else if asset_2 == *asset {
238					Some(asset_1)
239				} else {
240					None
241				}
242			},
243		)
244	}
245}
246
247#[cfg(test)]
248mod tests {
249	use super::*;
250	use pezsp_runtime::traits::MaybeEquivalence;
251	use xcm_builder::{StartsWithExplicitGlobalConsensus, WithLatestLocationConverter};
252	use xcm_executor::traits::{Error as MatchError, MatchesFungibles};
253
254	#[test]
255	fn asset_id_for_trust_backed_assets_convert_works() {
256		pezframe_support::parameter_types! {
257			pub TrustBackedAssetsPalletLocation: Location = Location::new(5, [PalletInstance(13)]);
258		}
259		let local_asset_id = 123456789 as AssetIdForTrustBackedAssets;
260		let expected_reverse_ref =
261			Location::new(5, [PalletInstance(13), GeneralIndex(local_asset_id.into())]);
262
263		assert_eq!(
264			AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert_back(
265				&local_asset_id
266			)
267			.unwrap(),
268			expected_reverse_ref
269		);
270		assert_eq!(
271			AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert(
272				&expected_reverse_ref
273			)
274			.unwrap(),
275			local_asset_id
276		);
277	}
278
279	#[test]
280	fn trust_backed_assets_match_fungibles_works() {
281		pezframe_support::parameter_types! {
282			pub TrustBackedAssetsPalletLocation: Location = Location::new(0, [PalletInstance(13)]);
283		}
284		// set up a converter
285		type TrustBackedAssetsConvert =
286			TrustBackedAssetsConvertedConcreteId<TrustBackedAssetsPalletLocation, u128>;
287
288		let test_data = vec![
289			// missing GeneralIndex
290			(ma_1000(0, [PalletInstance(13)].into()), Err(MatchError::AssetIdConversionFailed)),
291			(
292				ma_1000(0, [PalletInstance(13), GeneralKey { data: [0; 32], length: 32 }].into()),
293				Err(MatchError::AssetIdConversionFailed),
294			),
295			(
296				ma_1000(0, [PalletInstance(13), Teyrchain(1000)].into()),
297				Err(MatchError::AssetIdConversionFailed),
298			),
299			// OK
300			(ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()), Ok((1234, 1000))),
301			(
302				ma_1000(0, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
303				Ok((1234, 1000)),
304			),
305			(
306				ma_1000(
307					0,
308					[
309						PalletInstance(13),
310						GeneralIndex(1234),
311						GeneralIndex(2222),
312						GeneralKey { data: [0; 32], length: 32 },
313					]
314					.into(),
315				),
316				Ok((1234, 1000)),
317			),
318			// wrong pezpallet instance
319			(
320				ma_1000(0, [PalletInstance(77), GeneralIndex(1234)].into()),
321				Err(MatchError::AssetNotHandled),
322			),
323			(
324				ma_1000(0, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()),
325				Err(MatchError::AssetNotHandled),
326			),
327			// wrong parent
328			(
329				ma_1000(1, [PalletInstance(13), GeneralIndex(1234)].into()),
330				Err(MatchError::AssetNotHandled),
331			),
332			(
333				ma_1000(1, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
334				Err(MatchError::AssetNotHandled),
335			),
336			(
337				ma_1000(1, [PalletInstance(77), GeneralIndex(1234)].into()),
338				Err(MatchError::AssetNotHandled),
339			),
340			(
341				ma_1000(1, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()),
342				Err(MatchError::AssetNotHandled),
343			),
344			// wrong parent
345			(
346				ma_1000(2, [PalletInstance(13), GeneralIndex(1234)].into()),
347				Err(MatchError::AssetNotHandled),
348			),
349			(
350				ma_1000(2, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()),
351				Err(MatchError::AssetNotHandled),
352			),
353			// missing GeneralIndex
354			(ma_1000(0, [PalletInstance(77)].into()), Err(MatchError::AssetNotHandled)),
355			(ma_1000(1, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)),
356			(ma_1000(2, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)),
357		];
358
359		for (asset, expected_result) in test_data {
360			assert_eq!(
361				<TrustBackedAssetsConvert as MatchesFungibles<AssetIdForTrustBackedAssets, u128>>::matches_fungibles(&asset.clone().try_into().unwrap()),
362				expected_result, "asset: {:?}", asset);
363		}
364	}
365
366	#[test]
367	fn foreign_assets_converted_concrete_id_converter_works() {
368		pezframe_support::parameter_types! {
369			pub Teyrchain100Pattern: Location = Location::new(1, [Teyrchain(100)]);
370			pub UniversalLocationNetworkId: NetworkId = NetworkId::ByGenesis([9; 32]);
371		}
372
373		// set up a converter which uses `xcm::v4::Location` under the hood
374		type Convert = ForeignAssetsConvertedConcreteId<
375			(
376				StartsWith<Teyrchain100Pattern>,
377				StartsWithExplicitGlobalConsensus<UniversalLocationNetworkId>,
378			),
379			u128,
380			xcm::v4::Location,
381			WithLatestLocationConverter<xcm::v4::Location>,
382		>;
383
384		let test_data = vec![
385			// excluded as local
386			(ma_1000(0, Here), Err(MatchError::AssetNotHandled)),
387			(ma_1000(0, [Teyrchain(100)].into()), Err(MatchError::AssetNotHandled)),
388			(
389				ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()),
390				Err(MatchError::AssetNotHandled),
391			),
392			// excluded as parent
393			(ma_1000(1, Here), Err(MatchError::AssetNotHandled)),
394			// excluded as additional filter - Teyrchain100Pattern
395			(ma_1000(1, [Teyrchain(100)].into()), Err(MatchError::AssetNotHandled)),
396			(
397				ma_1000(1, [Teyrchain(100), GeneralIndex(1234)].into()),
398				Err(MatchError::AssetNotHandled),
399			),
400			(
401				ma_1000(1, [Teyrchain(100), PalletInstance(13), GeneralIndex(1234)].into()),
402				Err(MatchError::AssetNotHandled),
403			),
404			// excluded as additional filter - StartsWithExplicitGlobalConsensus
405			(
406				ma_1000(1, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()),
407				Err(MatchError::AssetNotHandled),
408			),
409			(
410				ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()),
411				Err(MatchError::AssetNotHandled),
412			),
413			(
414				ma_1000(
415					2,
416					[
417						GlobalConsensus(NetworkId::ByGenesis([9; 32])),
418						Teyrchain(200),
419						GeneralIndex(1234),
420					]
421					.into(),
422				),
423				Err(MatchError::AssetNotHandled),
424			),
425			// ok
426			(
427				ma_1000(1, [Teyrchain(200)].into()),
428				Ok((xcm::v4::Location::new(1, [xcm::v4::Junction::Teyrchain(200)]), 1000)),
429			),
430			(
431				ma_1000(2, [Teyrchain(200)].into()),
432				Ok((xcm::v4::Location::new(2, [xcm::v4::Junction::Teyrchain(200)]), 1000)),
433			),
434			(
435				ma_1000(1, [Teyrchain(200), GeneralIndex(1234)].into()),
436				Ok((
437					xcm::v4::Location::new(
438						1,
439						[xcm::v4::Junction::Teyrchain(200), xcm::v4::Junction::GeneralIndex(1234)],
440					),
441					1000,
442				)),
443			),
444			(
445				ma_1000(2, [Teyrchain(200), GeneralIndex(1234)].into()),
446				Ok((
447					xcm::v4::Location::new(
448						2,
449						[xcm::v4::Junction::Teyrchain(200), xcm::v4::Junction::GeneralIndex(1234)],
450					),
451					1000,
452				)),
453			),
454			(
455				ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([7; 32]))].into()),
456				Ok((
457					xcm::v4::Location::new(
458						2,
459						[xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis(
460							[7; 32],
461						))],
462					),
463					1000,
464				)),
465			),
466			(
467				ma_1000(
468					2,
469					[
470						GlobalConsensus(NetworkId::ByGenesis([7; 32])),
471						Teyrchain(200),
472						GeneralIndex(1234),
473					]
474					.into(),
475				),
476				Ok((
477					xcm::v4::Location::new(
478						2,
479						[
480							xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis(
481								[7; 32],
482							)),
483							xcm::v4::Junction::Teyrchain(200),
484							xcm::v4::Junction::GeneralIndex(1234),
485						],
486					),
487					1000,
488				)),
489			),
490		];
491
492		for (asset, expected_result) in test_data {
493			assert_eq!(
494				<Convert as MatchesFungibles<xcm::v4::Location, u128>>::matches_fungibles(
495					&asset.clone().try_into().unwrap()
496				),
497				expected_result,
498				"asset: {:?}",
499				asset
500			);
501		}
502	}
503
504	// Create Asset
505	fn ma_1000(parents: u8, interior: Junctions) -> Asset {
506		(Location::new(parents, interior), 1000).into()
507	}
508}