use super::MintLocation;
use core::{marker::PhantomData, result};
use frame_support::traits::{
tokens::{
fungible,
Fortitude::Polite,
Precision::Exact,
Preservation::{Expendable, Preserve},
Provenance::Minted,
},
Get,
};
use xcm::latest::prelude::*;
use xcm_executor::{
traits::{ConvertLocation, Error as MatchError, MatchesFungible, TransactAsset},
AssetsInHolding,
};
pub struct FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId)>,
);
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone,
> TransactAsset for FungibleTransferAdapter<Fungible, Matcher, AccountIdConverter, AccountId>
{
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
_context: &XcmContext,
) -> result::Result<AssetsInHolding, XcmError> {
log::trace!(
target: "xcm::fungible_adapter",
"internal_transfer_asset what: {:?}, from: {:?}, to: {:?}",
what, from, to
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
let source = AccountIdConverter::convert_location(from)
.ok_or(MatchError::AccountIdConversionFailed)?;
let dest = AccountIdConverter::convert_location(to)
.ok_or(MatchError::AccountIdConversionFailed)?;
Fungible::transfer(&source, &dest, amount, Preserve)
.map_err(|error| XcmError::FailedToTransactAsset(error.into()))?;
Ok(what.clone().into())
}
}
pub struct FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
);
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone,
CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
{
fn can_accrue_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
Fungible::can_deposit(&checking_account, amount, Minted)
.into_result()
.map_err(|_| XcmError::NotDepositable)
}
fn can_reduce_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult {
Fungible::can_withdraw(&checking_account, amount)
.into_result(false)
.map_err(|_| XcmError::NotWithdrawable)
.map(|_| ())
}
fn accrue_checked(checking_account: AccountId, amount: Fungible::Balance) {
let ok = Fungible::mint_into(&checking_account, amount).is_ok();
debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
}
fn reduce_checked(checking_account: AccountId, amount: Fungible::Balance) {
let ok = Fungible::burn_from(&checking_account, amount, Expendable, Exact, Polite).is_ok();
debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
}
}
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone,
CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for FungibleMutateAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
{
fn can_check_in(_origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungible_adapter",
"can_check_in origin: {:?}, what: {:?}",
_origin, what
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::can_reduce_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::can_accrue_checked(checking_account, amount),
None => Ok(()),
}
}
fn check_in(_origin: &Location, what: &Asset, _context: &XcmContext) {
log::trace!(
target: "xcm::fungible_adapter",
"check_in origin: {:?}, what: {:?}",
_origin, what
);
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::reduce_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::accrue_checked(checking_account, amount),
None => (),
}
}
}
fn can_check_out(_dest: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungible_adapter",
"can_check_out dest: {:?}, what: {:?}",
_dest,
what
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::can_accrue_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::can_reduce_checked(checking_account, amount),
None => Ok(()),
}
}
fn check_out(_dest: &Location, what: &Asset, _context: &XcmContext) {
log::trace!(
target: "xcm::fungible_adapter",
"check_out dest: {:?}, what: {:?}",
_dest,
what
);
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckingAccount::get() {
Some((checking_account, MintLocation::Local)) =>
Self::accrue_checked(checking_account, amount),
Some((checking_account, MintLocation::NonLocal)) =>
Self::reduce_checked(checking_account, amount),
None => (),
}
}
}
fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
log::trace!(
target: "xcm::fungible_adapter",
"deposit_asset what: {:?}, who: {:?}",
what, who,
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Fungible::mint_into(&who, amount)
.map_err(|error| XcmError::FailedToTransactAsset(error.into()))?;
Ok(())
}
fn withdraw_asset(
what: &Asset,
who: &Location,
_context: Option<&XcmContext>,
) -> result::Result<AssetsInHolding, XcmError> {
log::trace!(
target: "xcm::fungible_adapter",
"withdraw_asset what: {:?}, who: {:?}",
what, who,
);
let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Fungible::burn_from(&who, amount, Expendable, Exact, Polite)
.map_err(|error| XcmError::FailedToTransactAsset(error.into()))?;
Ok(what.clone().into())
}
}
pub struct FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>(
PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>,
);
impl<
Fungible: fungible::Mutate<AccountId>,
Matcher: MatchesFungible<Fungible::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone,
CheckingAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for FungibleAdapter<Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount>
{
fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_in(origin, what, context)
}
fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_in(origin, what, context)
}
fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_out(dest, what, context)
}
fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_out(dest, what, context)
}
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::deposit_asset(what, who, context)
}
fn withdraw_asset(
what: &Asset,
who: &Location,
maybe_context: Option<&XcmContext>,
) -> result::Result<AssetsInHolding, XcmError> {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::withdraw_asset(what, who, maybe_context)
}
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
context: &XcmContext,
) -> result::Result<AssetsInHolding, XcmError> {
FungibleTransferAdapter::<Fungible, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
what, from, to, context
)
}
}