// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] pub mod benchmarks; mod erc20_transactor; pub mod foreign_creators; pub mod fungible_conversion; pub mod local_and_foreign_assets; pub mod matching; pub mod migrations; pub mod runtime_api; pub use erc20_transactor::ERC20Transactor; extern crate alloc; extern crate core; use crate::matching::{LocalLocationPattern, ParentLocation}; use alloc::vec::Vec; use codec::{Decode, EncodeLike}; use core::{cmp::PartialEq, marker::PhantomData}; use pezframe_support::traits::{Contains, Equals, EverythingBut}; use pezsp_core::H160; use pezsp_runtime::traits::{MaybeEquivalence, TryConvertInto}; use teyrchains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; use xcm::prelude::*; use xcm_builder::{ AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter, }; use xcm_executor::traits::JustTry; /// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` pub type AssetIdForTrustBackedAssetsConvert = AsPrefixedGeneralIndex< TrustBackedAssetsPalletLocation, AssetIdForTrustBackedAssets, TryConvertInto, L, >; /// `Location` vs `CollectionId` converter for `Uniques` pub type CollectionIdForUniquesConvert = AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets` pub type TrustBackedAssetsConvertedConcreteId< TrustBackedAssetsPalletLocation, Balance, L = Location, > = MatchedConvertedConcreteId< AssetIdForTrustBackedAssets, Balance, StartsWith, AssetIdForTrustBackedAssetsConvert, TryConvertInto, >; /// [`MatchedConvertedConcreteId`] converter dedicated for `Uniques` pub type UniquesConvertedConcreteId = MatchedConvertedConcreteId< CollectionId, ItemId, // The asset starts with the uniques pezpallet. The `CollectionId` of the asset is specified as // a junction within the pezpallet itself. StartsWith, CollectionIdForUniquesConvert, TryConvertInto, >; /// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`, /// it is a similar implementation to `TrustBackedAssetsConvertedConcreteId`, /// but it converts `AssetId` to `xcm::v*::Location` type instead of `AssetIdForTrustBackedAssets = /// u32` pub type TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, Balance, L, LocationConverter = WithLatestLocationConverter, > = MatchedConvertedConcreteId< L, Balance, StartsWith, LocationConverter, TryConvertInto, >; /// [`MatchedConvertedConcreteId`] converter dedicated for storing `ForeignAssets` with `AssetId` as /// `Location`. /// /// Excludes by default: /// - parent as relay chain /// - all local Locations /// /// `AdditionalLocationExclusionFilter` can customize additional excluded Locations pub type ForeignAssetsConvertedConcreteId< AdditionalLocationExclusionFilter, Balance, AssetId, LocationToAssetIdConverter = WithLatestLocationConverter, BalanceConverter = TryConvertInto, > = MatchedConvertedConcreteId< AssetId, Balance, EverythingBut<( // Excludes relay/parent chain currency Equals, // Here we rely on fact that something like this works: // assert!(Location::new(1, // [Teyrchain(100)]).starts_with(&Location::parent())); // assert!([Teyrchain(100)].into().starts_with(&Here)); StartsWith, // Here we can exclude more stuff or leave it as `()` AdditionalLocationExclusionFilter, )>, LocationToAssetIdConverter, BalanceConverter, >; /// `Contains` implementation that matches locations with no parents, /// a `PalletInstance` and an `AccountKey20` junction. pub struct IsLocalAccountKey20; impl Contains for IsLocalAccountKey20 { fn contains(location: &Location) -> bool { matches!(location.unpack(), (0, [AccountKey20 { .. }])) } } /// Fallible converter from a location to a `H160` that matches any location ending with /// an `AccountKey20` junction. pub struct AccountKey20ToH160; impl MaybeEquivalence for AccountKey20ToH160 { fn convert(location: &Location) -> Option { match location.unpack() { (0, [AccountKey20 { key, .. }]) => Some((*key).into()), _ => None, } } fn convert_back(key: &H160) -> Option { Some(Location::new(0, [AccountKey20 { key: (*key).into(), network: None }])) } } /// [`xcm_executor::traits::MatchesFungibles`] implementation that matches /// ERC20 tokens. pub type ERC20Matcher = MatchedConvertedConcreteId; pub type AssetIdForPoolAssets = u32; /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`. pub type AssetIdForPoolAssetsConvert = AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets` pub type PoolAssetsConvertedConcreteId = MatchedConvertedConcreteId< AssetIdForPoolAssets, Balance, StartsWith, AssetIdForPoolAssetsConvert, TryConvertInto, >; /// Adapter implementation for accessing pools (`pezpallet_asset_conversion`) that uses `AssetKind` /// as a `xcm::v*` which could be different from the `xcm::latest`. pub struct PoolAdapter(PhantomData); impl< Runtime: pezpallet_asset_conversion::Config, L: TryFrom + TryInto + Clone + Decode + EncodeLike + PartialEq, > PoolAdapter { /// Returns a vector of all assets in a pool with `asset`. /// /// Should only be used in runtime APIs since it iterates over the whole /// `pezpallet_asset_conversion::Pools` map. /// /// It takes in any version of an XCM Location but always returns the latest one. /// This is to allow some margin of migrating the pools when updating the XCM version. /// /// An error of type `()` is returned if the version conversion fails for XCM locations. /// This error should be mapped by the caller to a more descriptive one. pub fn get_assets_in_pool_with(asset: Location) -> Result, ()> { // convert latest to the `L` version. let asset: L = asset.try_into().map_err(|_| ())?; Self::iter_assets_in_pool_with(&asset) .map(|location| { // convert `L` to the latest `AssetId` location.try_into().map_err(|_| ()).map(AssetId) }) .collect::, _>>() } /// Provides a current prices. Wrapper over /// `pezpallet_asset_conversion::Pezpallet::::quote_price_tokens_for_exact_tokens`. /// /// An error of type `()` is returned if the version conversion fails for XCM locations. /// This error should be mapped by the caller to a more descriptive one. pub fn quote_price_tokens_for_exact_tokens( asset_1: Location, asset_2: Location, amount: Runtime::Balance, include_fees: bool, ) -> Result, ()> { // Convert latest to the `L` version. let asset_1: L = asset_1.try_into().map_err(|_| ())?; let asset_2: L = asset_2.try_into().map_err(|_| ())?; // Quote swap price. Ok(pezpallet_asset_conversion::Pezpallet::::quote_price_tokens_for_exact_tokens( asset_1, asset_2, amount, include_fees, )) } /// Helper function for filtering pool. pub fn iter_assets_in_pool_with(asset: &L) -> impl Iterator + '_ { pezpallet_asset_conversion::Pools::::iter_keys().filter_map( |(asset_1, asset_2)| { if asset_1 == *asset { Some(asset_2) } else if asset_2 == *asset { Some(asset_1) } else { None } }, ) } } #[cfg(test)] mod tests { use super::*; use pezsp_runtime::traits::MaybeEquivalence; use xcm_builder::{StartsWithExplicitGlobalConsensus, WithLatestLocationConverter}; use xcm_executor::traits::{Error as MatchError, MatchesFungibles}; #[test] fn asset_id_for_trust_backed_assets_convert_works() { pezframe_support::parameter_types! { pub TrustBackedAssetsPalletLocation: Location = Location::new(5, [PalletInstance(13)]); } let local_asset_id = 123456789 as AssetIdForTrustBackedAssets; let expected_reverse_ref = Location::new(5, [PalletInstance(13), GeneralIndex(local_asset_id.into())]); assert_eq!( AssetIdForTrustBackedAssetsConvert::::convert_back( &local_asset_id ) .unwrap(), expected_reverse_ref ); assert_eq!( AssetIdForTrustBackedAssetsConvert::::convert( &expected_reverse_ref ) .unwrap(), local_asset_id ); } #[test] fn trust_backed_assets_match_fungibles_works() { pezframe_support::parameter_types! { pub TrustBackedAssetsPalletLocation: Location = Location::new(0, [PalletInstance(13)]); } // set up a converter type TrustBackedAssetsConvert = TrustBackedAssetsConvertedConcreteId; let test_data = vec![ // missing GeneralIndex (ma_1000(0, [PalletInstance(13)].into()), Err(MatchError::AssetIdConversionFailed)), ( ma_1000(0, [PalletInstance(13), GeneralKey { data: [0; 32], length: 32 }].into()), Err(MatchError::AssetIdConversionFailed), ), ( ma_1000(0, [PalletInstance(13), Teyrchain(1000)].into()), Err(MatchError::AssetIdConversionFailed), ), // OK (ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()), Ok((1234, 1000))), ( ma_1000(0, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()), Ok((1234, 1000)), ), ( ma_1000( 0, [ PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222), GeneralKey { data: [0; 32], length: 32 }, ] .into(), ), Ok((1234, 1000)), ), // wrong pezpallet instance ( ma_1000(0, [PalletInstance(77), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000(0, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), // wrong parent ( ma_1000(1, [PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000(1, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000(1, [PalletInstance(77), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000(1, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), // wrong parent ( ma_1000(2, [PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000(2, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), // missing GeneralIndex (ma_1000(0, [PalletInstance(77)].into()), Err(MatchError::AssetNotHandled)), (ma_1000(1, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)), (ma_1000(2, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)), ]; for (asset, expected_result) in test_data { assert_eq!( >::matches_fungibles(&asset.clone().try_into().unwrap()), expected_result, "asset: {:?}", asset); } } #[test] fn foreign_assets_converted_concrete_id_converter_works() { pezframe_support::parameter_types! { pub Teyrchain100Pattern: Location = Location::new(1, [Teyrchain(100)]); pub UniversalLocationNetworkId: NetworkId = NetworkId::ByGenesis([9; 32]); } // set up a converter which uses `xcm::v4::Location` under the hood type Convert = ForeignAssetsConvertedConcreteId< ( StartsWith, StartsWithExplicitGlobalConsensus, ), u128, xcm::v4::Location, WithLatestLocationConverter, >; let test_data = vec![ // excluded as local (ma_1000(0, Here), Err(MatchError::AssetNotHandled)), (ma_1000(0, [Teyrchain(100)].into()), Err(MatchError::AssetNotHandled)), ( ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), // excluded as parent (ma_1000(1, Here), Err(MatchError::AssetNotHandled)), // excluded as additional filter - Teyrchain100Pattern (ma_1000(1, [Teyrchain(100)].into()), Err(MatchError::AssetNotHandled)), ( ma_1000(1, [Teyrchain(100), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000(1, [Teyrchain(100), PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), // excluded as additional filter - StartsWithExplicitGlobalConsensus ( ma_1000(1, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000( 2, [ GlobalConsensus(NetworkId::ByGenesis([9; 32])), Teyrchain(200), GeneralIndex(1234), ] .into(), ), Err(MatchError::AssetNotHandled), ), // ok ( ma_1000(1, [Teyrchain(200)].into()), Ok((xcm::v4::Location::new(1, [xcm::v4::Junction::Teyrchain(200)]), 1000)), ), ( ma_1000(2, [Teyrchain(200)].into()), Ok((xcm::v4::Location::new(2, [xcm::v4::Junction::Teyrchain(200)]), 1000)), ), ( ma_1000(1, [Teyrchain(200), GeneralIndex(1234)].into()), Ok(( xcm::v4::Location::new( 1, [xcm::v4::Junction::Teyrchain(200), xcm::v4::Junction::GeneralIndex(1234)], ), 1000, )), ), ( ma_1000(2, [Teyrchain(200), GeneralIndex(1234)].into()), Ok(( xcm::v4::Location::new( 2, [xcm::v4::Junction::Teyrchain(200), xcm::v4::Junction::GeneralIndex(1234)], ), 1000, )), ), ( ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([7; 32]))].into()), Ok(( xcm::v4::Location::new( 2, [xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis( [7; 32], ))], ), 1000, )), ), ( ma_1000( 2, [ GlobalConsensus(NetworkId::ByGenesis([7; 32])), Teyrchain(200), GeneralIndex(1234), ] .into(), ), Ok(( xcm::v4::Location::new( 2, [ xcm::v4::Junction::GlobalConsensus(xcm::v4::NetworkId::ByGenesis( [7; 32], )), xcm::v4::Junction::Teyrchain(200), xcm::v4::Junction::GeneralIndex(1234), ], ), 1000, )), ), ]; for (asset, expected_result) in test_data { assert_eq!( >::matches_fungibles( &asset.clone().try_into().unwrap() ), expected_result, "asset: {:?}", asset ); } } // Create Asset fn ma_1000(parents: u8, interior: Junctions) -> Asset { (Location::new(parents, interior), 1000).into() } }