use crate::MintLocation;
use core::{fmt::Debug, marker::PhantomData, result};
use frame_support::{
ensure,
traits::{tokens::nonfungible, Get},
};
use xcm::latest::prelude::*;
use xcm_executor::traits::{
ConvertLocation, Error as MatchError, MatchesNonFungible, TransactAsset,
};
const LOG_TARGET: &str = "xcm::nonfungible_adapter";
pub struct NonFungibleTransferAdapter<NonFungible, Matcher, AccountIdConverter, AccountId>(
PhantomData<(NonFungible, Matcher, AccountIdConverter, AccountId)>,
)
where
NonFungible: nonfungible::Transfer<AccountId>;
impl<
NonFungible: nonfungible::Transfer<AccountId>,
Matcher: MatchesNonFungible<NonFungible::ItemId>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone + Debug, > TransactAsset for NonFungibleTransferAdapter<NonFungible, Matcher, AccountIdConverter, AccountId>
where
NonFungible::ItemId: Debug,
{
fn transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
context: &XcmContext,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
tracing::trace!(
target: LOG_TARGET,
?what,
?from,
?to,
?context,
"transfer_asset",
);
let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?;
let destination = AccountIdConverter::convert_location(to)
.ok_or(MatchError::AccountIdConversionFailed)?;
NonFungible::transfer(&instance, &destination).map_err(|e| {
tracing::debug!(target: LOG_TARGET, ?e, ?instance, ?destination, "Failed to transfer non-fungible asset");
XcmError::FailedToTransactAsset(e.into())
})?;
Ok(what.clone().into())
}
}
pub struct NonFungibleMutateAdapter<
NonFungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>(PhantomData<(NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>)
where
NonFungible: nonfungible::Mutate<AccountId>,
NonFungible::ItemId: Debug;
impl<
NonFungible: nonfungible::Mutate<AccountId>,
Matcher: MatchesNonFungible<NonFungible::ItemId>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone + Eq + Debug, CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> NonFungibleMutateAdapter<NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
where
NonFungible::ItemId: Debug,
{
fn can_accrue_checked(instance: NonFungible::ItemId) -> XcmResult {
ensure!(NonFungible::owner(&instance).is_none(), XcmError::NotDepositable);
Ok(())
}
fn can_reduce_checked(checking_account: AccountId, instance: NonFungible::ItemId) -> XcmResult {
let owner = NonFungible::owner(&instance);
ensure!(owner == Some(checking_account), XcmError::NotWithdrawable);
ensure!(NonFungible::can_transfer(&instance), XcmError::NotWithdrawable);
Ok(())
}
fn accrue_checked(checking_account: AccountId, instance: NonFungible::ItemId) {
let ok = NonFungible::mint_into(&instance, &checking_account).is_ok();
debug_assert!(ok, "`mint_into` cannot generally fail; qed");
}
fn reduce_checked(instance: NonFungible::ItemId) {
let ok = NonFungible::burn(&instance, None).is_ok();
debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
}
}
impl<
NonFungible: nonfungible::Mutate<AccountId>,
Matcher: MatchesNonFungible<NonFungible::ItemId>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone + Eq + Debug, CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for NonFungibleMutateAdapter<NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
where
NonFungible::ItemId: Debug,
{
fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
tracing::trace!(
target: LOG_TARGET,
?origin,
?what,
?context,
"can_check_in",
);
let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?;
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::can_reduce_checked(checking_account, instance),
Some((_, MintLocation::NonLocal)) => Self::can_accrue_checked(instance),
_ => Ok(()),
}
}
fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
tracing::trace!(
target: LOG_TARGET,
?origin,
?what,
?context,
"check_in",
);
if let Some(instance) = Matcher::matches_nonfungible(what) {
match CheckingAccount::get() {
Some((_, MintLocation::Local)) => Self::reduce_checked(instance),
Some((checking_account, MintLocation::NonLocal)) =>
Self::accrue_checked(checking_account, instance),
_ => (),
}
}
}
fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
tracing::trace!(
target: LOG_TARGET,
?dest,
?what,
?context,
"can_check_out",
);
let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?;
match CheckingAccount::get() {
Some((_, MintLocation::Local)) => Self::can_accrue_checked(instance),
Some((checking_account, MintLocation::NonLocal)) =>
Self::can_reduce_checked(checking_account, instance),
_ => Ok(()),
}
}
fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
tracing::trace!(
target: LOG_TARGET,
?dest,
?what,
?context,
"check_out",
);
if let Some(instance) = Matcher::matches_nonfungible(what) {
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::accrue_checked(checking_account, instance),
Some((_, MintLocation::NonLocal)) => Self::reduce_checked(instance),
_ => (),
}
}
}
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
tracing::trace!(
target: LOG_TARGET,
?what,
?who,
?context,
"deposit_asset",
);
let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
NonFungible::mint_into(&instance, &who).map_err(|e| {
tracing::debug!(target: LOG_TARGET, ?e, ?instance, ?who, "Failed to mint asset");
XcmError::FailedToTransactAsset(e.into())
})
}
fn withdraw_asset(
what: &Asset,
who: &Location,
maybe_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
tracing::trace!(
target: LOG_TARGET,
?what,
?who,
?maybe_context,
"withdraw_asset"
);
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?;
NonFungible::burn(&instance, Some(&who)).map_err(|e| {
tracing::debug!(target: LOG_TARGET, ?e, ?instance, ?who, "Failed to burn asset");
XcmError::FailedToTransactAsset(e.into())
})?;
Ok(what.clone().into())
}
}
pub struct NonFungibleAdapter<NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
PhantomData<(NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
)
where
NonFungible: nonfungible::Mutate<AccountId> + nonfungible::Transfer<AccountId>,
NonFungible::ItemId: Debug;
impl<
NonFungible: nonfungible::Mutate<AccountId> + nonfungible::Transfer<AccountId>,
Matcher: MatchesNonFungible<NonFungible::ItemId>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone + Eq + Debug, CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for NonFungibleAdapter<NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
where
NonFungible::ItemId: Debug,
{
fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
NonFungibleMutateAdapter::<
NonFungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_in(origin, what, context)
}
fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
NonFungibleMutateAdapter::<
NonFungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_in(origin, what, context)
}
fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
NonFungibleMutateAdapter::<
NonFungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_out(dest, what, context)
}
fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
NonFungibleMutateAdapter::<
NonFungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_out(dest, what, context)
}
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
NonFungibleMutateAdapter::<
NonFungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::deposit_asset(what, who, context)
}
fn withdraw_asset(
what: &Asset,
who: &Location,
maybe_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
NonFungibleMutateAdapter::<
NonFungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::withdraw_asset(what, who, maybe_context)
}
fn transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
context: &XcmContext,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
NonFungibleTransferAdapter::<NonFungible, Matcher, AccountIdConverter, AccountId>::transfer_asset(
what, from, to, context,
)
}
}