// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! Adapters to work with `frame_support::traits::tokens::fungibles` through XCM. use frame_support::traits::{Contains, Get}; use sp_std::{borrow::Borrow, marker::PhantomData, prelude::*, result}; use xcm::latest::prelude::*; use xcm_executor::traits::{Convert, Error as MatchError, MatchesFungibles, MatchesNonFungibles}; /// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be `TryFrom/TryInto`) into /// a `GeneralIndex` junction, prefixed by some `MultiLocation` value. The `MultiLocation` value will typically be a /// `PalletInstance` junction. pub struct AsPrefixedGeneralIndex( PhantomData<(Prefix, AssetId, ConvertAssetId)>, ); impl, AssetId: Clone, ConvertAssetId: Convert> Convert for AsPrefixedGeneralIndex { fn convert_ref(id: impl Borrow) -> result::Result { let prefix = Prefix::get(); let id = id.borrow(); if prefix.parent_count() != id.parent_count() || prefix .interior() .iter() .enumerate() .any(|(index, junction)| id.interior().at(index) != Some(junction)) { return Err(()) } match id.interior().at(prefix.interior().len()) { Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert_ref(id), _ => Err(()), } } fn reverse_ref(what: impl Borrow) -> result::Result { let mut location = Prefix::get(); let id = ConvertAssetId::reverse_ref(what)?; location.push_interior(Junction::GeneralIndex(id)).map_err(|_| ())?; Ok(location) } } pub struct ConvertedConcreteId( PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>, ); impl< AssetId: Clone, Balance: Clone, ConvertAssetId: Convert, ConvertBalance: Convert, > MatchesFungibles for ConvertedConcreteId { fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { let (amount, id) = match (&a.fun, &a.id) { (Fungible(ref amount), Concrete(ref id)) => (amount, id), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?; let amount = ConvertBalance::convert_ref(amount) .map_err(|_| MatchError::AmountToBalanceConversionFailed)?; Ok((what, amount)) } } impl< ClassId: Clone, InstanceId: Clone, ConvertClassId: Convert, ConvertInstanceId: Convert, > MatchesNonFungibles for ConvertedConcreteId { fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { let (instance, class) = match (&a.fun, &a.id) { (NonFungible(ref instance), Concrete(ref class)) => (instance, class), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertClassId::convert_ref(class).map_err(|_| MatchError::AssetIdConversionFailed)?; let instance = ConvertInstanceId::convert_ref(instance) .map_err(|_| MatchError::InstanceConversionFailed)?; Ok((what, instance)) } } pub struct ConvertedAbstractId( PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>, ); impl< AssetId: Clone, Balance: Clone, ConvertAssetId: Convert<[u8; 32], AssetId>, ConvertBalance: Convert, > MatchesFungibles for ConvertedAbstractId { fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { let (amount, id) = match (&a.fun, &a.id) { (Fungible(ref amount), Abstract(ref id)) => (amount, id), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?; let amount = ConvertBalance::convert_ref(amount) .map_err(|_| MatchError::AmountToBalanceConversionFailed)?; Ok((what, amount)) } } impl< ClassId: Clone, InstanceId: Clone, ConvertClassId: Convert<[u8; 32], ClassId>, ConvertInstanceId: Convert, > MatchesNonFungibles for ConvertedAbstractId { fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { let (instance, class) = match (&a.fun, &a.id) { (NonFungible(ref instance), Abstract(ref class)) => (instance, class), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertClassId::convert_ref(class).map_err(|_| MatchError::AssetIdConversionFailed)?; let instance = ConvertInstanceId::convert_ref(instance) .map_err(|_| MatchError::InstanceConversionFailed)?; Ok((what, instance)) } } #[deprecated = "Use `ConvertedConcreteId` instead"] pub type ConvertedConcreteAssetId = ConvertedConcreteId; #[deprecated = "Use `ConvertedAbstractId` instead"] pub type ConvertedAbstractAssetId = ConvertedAbstractId; pub struct MatchedConvertedConcreteId( PhantomData<(AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther)>, ); impl< AssetId: Clone, Balance: Clone, MatchAssetId: Contains, ConvertAssetId: Convert, ConvertBalance: Convert, > MatchesFungibles for MatchedConvertedConcreteId { fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { let (amount, id) = match (&a.fun, &a.id) { (Fungible(ref amount), Concrete(ref id)) if MatchAssetId::contains(id) => (amount, id), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertAssetId::convert_ref(id).map_err(|_| MatchError::AssetIdConversionFailed)?; let amount = ConvertBalance::convert_ref(amount) .map_err(|_| MatchError::AmountToBalanceConversionFailed)?; Ok((what, amount)) } } impl< ClassId: Clone, InstanceId: Clone, MatchClassId: Contains, ConvertClassId: Convert, ConvertInstanceId: Convert, > MatchesNonFungibles for MatchedConvertedConcreteId { fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { let (instance, class) = match (&a.fun, &a.id) { (NonFungible(ref instance), Concrete(ref class)) if MatchClassId::contains(class) => (instance, class), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertClassId::convert_ref(class).map_err(|_| MatchError::AssetIdConversionFailed)?; let instance = ConvertInstanceId::convert_ref(instance) .map_err(|_| MatchError::InstanceConversionFailed)?; Ok((what, instance)) } } #[cfg(test)] mod tests { use super::*; use xcm_executor::traits::JustTry; struct OnlyParentZero; impl Contains for OnlyParentZero { fn contains(a: &MultiLocation) -> bool { match a { MultiLocation { parents: 0, .. } => true, _ => false, } } } #[test] fn matched_converted_concrete_id_for_fungibles_works() { type AssetIdForTrustBackedAssets = u32; type Balance = u128; frame_support::parameter_types! { pub TrustBackedAssetsPalletLocation: MultiLocation = PalletInstance(50).into(); } // ConvertedConcreteId cfg type Converter = MatchedConvertedConcreteId< AssetIdForTrustBackedAssets, Balance, OnlyParentZero, AsPrefixedGeneralIndex< TrustBackedAssetsPalletLocation, AssetIdForTrustBackedAssets, JustTry, >, JustTry, >; assert_eq!( TrustBackedAssetsPalletLocation::get(), MultiLocation { parents: 0, interior: X1(PalletInstance(50)) } ); // err - does not match assert_eq!( Converter::matches_fungibles(&MultiAsset { id: Concrete(MultiLocation::new(1, X2(PalletInstance(50), GeneralIndex(1)))), fun: Fungible(12345), }), Err(MatchError::AssetNotHandled) ); // err - matches, but convert fails assert_eq!( Converter::matches_fungibles(&MultiAsset { id: Concrete(MultiLocation::new( 0, X2(PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }) )), fun: Fungible(12345), }), Err(MatchError::AssetIdConversionFailed) ); // err - matches, but NonFungible assert_eq!( Converter::matches_fungibles(&MultiAsset { id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), fun: NonFungible(Index(54321)), }), Err(MatchError::AssetNotHandled) ); // ok assert_eq!( Converter::matches_fungibles(&MultiAsset { id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), fun: Fungible(12345), }), Ok((1, 12345)) ); } #[test] fn matched_converted_concrete_id_for_nonfungibles_works() { type ClassId = u32; type ClassInstanceId = u64; frame_support::parameter_types! { pub TrustBackedAssetsPalletLocation: MultiLocation = PalletInstance(50).into(); } // ConvertedConcreteId cfg struct ClassInstanceIdConverter; impl Convert for ClassInstanceIdConverter { fn convert_ref(value: impl Borrow) -> Result { value.borrow().clone().try_into().map_err(|_| ()) } fn reverse_ref(value: impl Borrow) -> Result { Ok(AssetInstance::from(value.borrow().clone())) } } type Converter = MatchedConvertedConcreteId< ClassId, ClassInstanceId, OnlyParentZero, AsPrefixedGeneralIndex, ClassInstanceIdConverter, >; assert_eq!( TrustBackedAssetsPalletLocation::get(), MultiLocation { parents: 0, interior: X1(PalletInstance(50)) } ); // err - does not match assert_eq!( Converter::matches_nonfungibles(&MultiAsset { id: Concrete(MultiLocation::new(1, X2(PalletInstance(50), GeneralIndex(1)))), fun: NonFungible(Index(54321)), }), Err(MatchError::AssetNotHandled) ); // err - matches, but convert fails assert_eq!( Converter::matches_nonfungibles(&MultiAsset { id: Concrete(MultiLocation::new( 0, X2(PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }) )), fun: NonFungible(Index(54321)), }), Err(MatchError::AssetIdConversionFailed) ); // err - matches, but Fungible vs NonFungible assert_eq!( Converter::matches_nonfungibles(&MultiAsset { id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), fun: Fungible(12345), }), Err(MatchError::AssetNotHandled) ); // ok assert_eq!( Converter::matches_nonfungibles(&MultiAsset { id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), fun: NonFungible(Index(54321)), }), Ok((1, 54321)) ); } }