diff --git a/xcm-support/src/currency_adapter.rs b/xcm-support/src/currency_adapter.rs index 5eb76945f..6b8cf0b8f 100644 --- a/xcm-support/src/currency_adapter.rs +++ b/xcm-support/src/currency_adapter.rs @@ -11,12 +11,12 @@ use sp_std::{ use xcm::v0::{Error as XcmError, MultiAsset, MultiLocation, Result}; use xcm_executor::traits::{LocationConversion, MatchesFungible, TransactAsset}; -use crate::CurrencyIdConversion; +use crate::{CurrencyIdConversion, UnknownAsset as UnknownAssetT}; /// Asset transaction errors. enum Error { - /// Asset not found. - AssetNotFound, + /// Failed to match fungible. + FailedToMatchFungible, /// `MultiLocation` to `AccountId` Conversion failed. AccountIdConversionFailed, /// `CurrencyId` conversion failed. @@ -26,16 +26,29 @@ enum Error { impl From for XcmError { fn from(e: Error) -> Self { match e { - Error::AssetNotFound => XcmError::FailedToTransactAsset("AssetNotFound"), + Error::FailedToMatchFungible => XcmError::FailedToTransactAsset("FailedToMatchFungible"), Error::AccountIdConversionFailed => XcmError::FailedToTransactAsset("AccountIdConversionFailed"), Error::CurrencyIdConversionFailed => XcmError::FailedToTransactAsset("CurrencyIdConversionFailed"), } } } -pub struct MultiCurrencyAdapter( +/// The `TransactAsset` implementation, to handle `MultiAsset` deposit/withdraw. +/// +/// If the asset is known, deposit/withdraw will be handled by `MultiCurrency`, +/// else by `UnknownAsset` if unknown. +pub struct MultiCurrencyAdapter< + MultiCurrency, + UnknownAsset, + Matcher, + AccountIdConverter, + AccountId, + CurrencyIdConverter, + CurrencyId, +>( PhantomData<( MultiCurrency, + UnknownAsset, Matcher, AccountIdConverter, AccountId, @@ -46,35 +59,50 @@ pub struct MultiCurrencyAdapter, + UnknownAsset: UnknownAssetT, Matcher: MatchesFungible, AccountIdConverter: LocationConversion, AccountId: sp_std::fmt::Debug, CurrencyIdConverter: CurrencyIdConversion, CurrencyId: FullCodec + Eq + PartialEq + Copy + MaybeSerializeDeserialize + Debug, > TransactAsset - for MultiCurrencyAdapter + for MultiCurrencyAdapter< + MultiCurrency, + UnknownAsset, + Matcher, + AccountIdConverter, + AccountId, + CurrencyIdConverter, + CurrencyId, + > { fn deposit_asset(asset: &MultiAsset, location: &MultiLocation) -> Result { - let who = AccountIdConverter::from_location(location) - .ok_or_else(|| XcmError::from(Error::AccountIdConversionFailed))?; - let currency_id = - CurrencyIdConverter::from_asset(asset).ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; - let amount: MultiCurrency::Balance = Matcher::matches_fungible(&asset) - .ok_or_else(|| XcmError::from(Error::AssetNotFound))? - .saturated_into(); - MultiCurrency::deposit(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; - Ok(()) + match ( + AccountIdConverter::from_location(location), + CurrencyIdConverter::from_asset(asset), + Matcher::matches_fungible(&asset), + ) { + // known asset + (Some(who), Some(currency_id), Some(amount)) => { + MultiCurrency::deposit(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into())) + } + // unknown asset + _ => UnknownAsset::deposit(asset, location).map_err(|e| XcmError::FailedToTransactAsset(e.into())), + } } fn withdraw_asset(asset: &MultiAsset, location: &MultiLocation) -> result::Result { - let who = AccountIdConverter::from_location(location) - .ok_or_else(|| XcmError::from(Error::AccountIdConversionFailed))?; - let currency_id = - CurrencyIdConverter::from_asset(asset).ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; - let amount: MultiCurrency::Balance = Matcher::matches_fungible(&asset) - .ok_or_else(|| XcmError::from(Error::AssetNotFound))? - .saturated_into(); - MultiCurrency::withdraw(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + UnknownAsset::withdraw(asset, location).or_else(|_| { + let who = AccountIdConverter::from_location(location) + .ok_or_else(|| XcmError::from(Error::AccountIdConversionFailed))?; + let currency_id = CurrencyIdConverter::from_asset(asset) + .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; + let amount: MultiCurrency::Balance = Matcher::matches_fungible(&asset) + .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? + .saturated_into(); + MultiCurrency::withdraw(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into())) + })?; + Ok(asset.clone()) } } diff --git a/xcm-support/src/lib.rs b/xcm-support/src/lib.rs index e3a4aaacc..e95d3c2f7 100644 --- a/xcm-support/src/lib.rs +++ b/xcm-support/src/lib.rs @@ -8,7 +8,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{dispatch::DispatchResult, traits::Get}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + traits::Get, +}; use sp_runtime::traits::{CheckedConversion, Convert}; use sp_std::{ collections::btree_set::BTreeSet, @@ -111,3 +114,23 @@ where None } } + +/// Handlers unknown asset deposit and withdraw. +pub trait UnknownAsset { + /// Deposit unknown asset. + fn deposit(asset: &MultiAsset, to: &MultiLocation) -> DispatchResult; + + /// Withdraw unknown asset. + fn withdraw(asset: &MultiAsset, from: &MultiLocation) -> DispatchResult; +} + +const NO_UNKNOWN_ASSET_IMPL: &str = "NoUnknownAssetImpl"; + +impl UnknownAsset for () { + fn deposit(_asset: &MultiAsset, _to: &MultiLocation) -> DispatchResult { + Err(DispatchError::Other(NO_UNKNOWN_ASSET_IMPL)) + } + fn withdraw(_asset: &MultiAsset, _from: &MultiLocation) -> DispatchResult { + Err(DispatchError::Other(NO_UNKNOWN_ASSET_IMPL)) + } +}