use core::{marker::PhantomData, result};
use frame_support::traits::{
tokens::{
fungibles,
Fortitude::Polite,
Precision::Exact,
Preservation::{Expendable, Preserve},
Provenance::Minted,
},
Contains, Get,
};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset};
pub struct FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
);
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone, > TransactAsset for FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
{
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
_context: &XcmContext,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
log::trace!(
target: "xcm::fungibles_adapter",
"internal_transfer_asset what: {:?}, from: {:?}, to: {:?}",
what, from, to
);
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
let source = AccountIdConverter::convert_location(from)
.ok_or(MatchError::AccountIdConversionFailed)?;
let dest = AccountIdConverter::convert_location(to)
.ok_or(MatchError::AccountIdConversionFailed)?;
Assets::transfer(asset_id, &source, &dest, amount, Preserve)
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
Ok(what.clone().into())
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum MintLocation {
Local,
NonLocal,
}
pub trait AssetChecking<AssetId> {
fn asset_checking(asset: &AssetId) -> Option<MintLocation>;
}
pub struct NoChecking;
impl<AssetId> AssetChecking<AssetId> for NoChecking {
fn asset_checking(_: &AssetId) -> Option<MintLocation> {
None
}
}
pub struct LocalMint<T>(core::marker::PhantomData<T>);
impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for LocalMint<T> {
fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
match T::contains(asset) {
true => Some(MintLocation::Local),
false => None,
}
}
}
pub struct NonLocalMint<T>(core::marker::PhantomData<T>);
impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for NonLocalMint<T> {
fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
match T::contains(asset) {
true => Some(MintLocation::NonLocal),
false => None,
}
}
}
pub struct DualMint<L, R>(core::marker::PhantomData<(L, R)>);
impl<AssetId, L: Contains<AssetId>, R: Contains<AssetId>> AssetChecking<AssetId>
for DualMint<L, R>
{
fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
if L::contains(asset) {
Some(MintLocation::Local)
} else if R::contains(asset) {
Some(MintLocation::NonLocal)
} else {
None
}
}
}
pub struct FungiblesMutateAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone, CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
>
FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
{
fn can_accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
let checking_account = CheckingAccount::get();
Assets::can_deposit(asset_id, &checking_account, amount, Minted)
.into_result()
.map_err(|_| XcmError::NotDepositable)
}
fn can_reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
let checking_account = CheckingAccount::get();
Assets::can_withdraw(asset_id, &checking_account, amount)
.into_result(false)
.map_err(|_| XcmError::NotWithdrawable)
.map(|_| ())
}
fn accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
let checking_account = CheckingAccount::get();
let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok();
debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
}
fn reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
let checking_account = CheckingAccount::get();
let ok = Assets::burn_from(asset_id, &checking_account, amount, Expendable, Exact, Polite)
.is_ok();
debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
}
}
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone, CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset
for FungiblesMutateAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>
{
fn can_check_in(_origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungibles_adapter",
"can_check_in origin: {:?}, what: {:?}",
_origin, what
);
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
match CheckAsset::asset_checking(&asset_id) {
Some(MintLocation::Local) => Self::can_reduce_checked(asset_id, amount),
Some(MintLocation::NonLocal) => Self::can_accrue_checked(asset_id, amount),
_ => Ok(()),
}
}
fn check_in(_origin: &Location, what: &Asset, _context: &XcmContext) {
log::trace!(
target: "xcm::fungibles_adapter",
"check_in origin: {:?}, what: {:?}",
_origin, what
);
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
match CheckAsset::asset_checking(&asset_id) {
Some(MintLocation::Local) => Self::reduce_checked(asset_id, amount),
Some(MintLocation::NonLocal) => Self::accrue_checked(asset_id, amount),
_ => (),
}
}
}
fn can_check_out(_origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungibles_adapter",
"can_check_out origin: {:?}, what: {:?}",
_origin, what
);
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
match CheckAsset::asset_checking(&asset_id) {
Some(MintLocation::Local) => Self::can_accrue_checked(asset_id, amount),
Some(MintLocation::NonLocal) => Self::can_reduce_checked(asset_id, amount),
_ => Ok(()),
}
}
fn check_out(_dest: &Location, what: &Asset, _context: &XcmContext) {
log::trace!(
target: "xcm::fungibles_adapter",
"check_out dest: {:?}, what: {:?}",
_dest, what
);
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
match CheckAsset::asset_checking(&asset_id) {
Some(MintLocation::Local) => Self::accrue_checked(asset_id, amount),
Some(MintLocation::NonLocal) => Self::reduce_checked(asset_id, amount),
_ => (),
}
}
}
fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
log::trace!(
target: "xcm::fungibles_adapter",
"deposit_asset what: {:?}, who: {:?}",
what, who,
);
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Assets::mint_into(asset_id, &who, amount)
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
Ok(())
}
fn withdraw_asset(
what: &Asset,
who: &Location,
_maybe_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
log::trace!(
target: "xcm::fungibles_adapter",
"withdraw_asset what: {:?}, who: {:?}",
what, who,
);
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
let who = AccountIdConverter::convert_location(who)
.ok_or(MatchError::AccountIdConversionFailed)?;
Assets::burn_from(asset_id, &who, amount, Expendable, Exact, Polite)
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
Ok(what.clone().into())
}
}
pub struct FungiblesAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Eq + Clone, CheckAsset: AssetChecking<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset
for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
{
fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::can_check_in(origin, what, context)
}
fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::check_in(origin, what, context)
}
fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::can_check_out(dest, what, context)
}
fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::check_out(dest, what, context)
}
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::deposit_asset(what, who, context)
}
fn withdraw_asset(
what: &Asset,
who: &Location,
maybe_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
FungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::withdraw_asset(what, who, maybe_context)
}
fn internal_transfer_asset(
what: &Asset,
from: &Location,
to: &Location,
context: &XcmContext,
) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
FungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
what, from, to, context
)
}
}