// Copyright (C) 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 crate::{AssetChecking, MintLocation};
use frame_support::{
ensure,
traits::{tokens::nonfungibles, Get},
};
use sp_std::{marker::PhantomData, prelude::*, result};
use xcm::latest::prelude::*;
use xcm_executor::traits::{Convert, Error as MatchError, MatchesNonFungibles, TransactAsset};
pub struct NonFungiblesTransferAdapter(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
);
impl<
Assets: nonfungibles::Transfer,
Matcher: MatchesNonFungibles,
AccountIdConverter: Convert,
AccountId: Clone, // can't get away without it since Currency is generic over it.
> TransactAsset for NonFungiblesTransferAdapter
{
fn transfer_asset(
what: &MultiAsset,
from: &MultiLocation,
to: &MultiLocation,
context: &XcmContext,
) -> result::Result {
log::trace!(
target: "xcm::non_fungibles_adapter",
"transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}",
what, from, to, context,
);
// Check we handle this asset.
let (class, instance) = Matcher::matches_nonfungibles(what)?;
let destination = AccountIdConverter::convert_ref(to)
.map_err(|()| MatchError::AccountIdConversionFailed)?;
Assets::transfer(&class, &instance, &destination)
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
Ok(what.clone().into())
}
}
pub struct NonFungiblesMutateAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
impl<
Assets: nonfungibles::Mutate,
Matcher: MatchesNonFungibles,
AccountIdConverter: Convert,
AccountId: Clone + Eq, // can't get away without it since Currency is generic over it.
CheckAsset: AssetChecking,
CheckingAccount: Get>,
>
NonFungiblesMutateAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>
{
fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable);
Ok(())
}
fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
if let Some(checking_account) = CheckingAccount::get() {
// This is an asset whose teleports we track.
let owner = Assets::owner(&class, &instance);
ensure!(owner == Some(checking_account), XcmError::NotWithdrawable);
ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable);
}
Ok(())
}
fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
if let Some(checking_account) = CheckingAccount::get() {
let ok = Assets::mint_into(&class, &instance, &checking_account).is_ok();
debug_assert!(ok, "`mint_into` cannot generally fail; qed");
}
}
fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
let ok = Assets::burn(&class, &instance, None).is_ok();
debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
}
}
impl<
Assets: nonfungibles::Mutate,
Matcher: MatchesNonFungibles,
AccountIdConverter: Convert,
AccountId: Clone + Eq, // can't get away without it since Currency is generic over it.
CheckAsset: AssetChecking,
CheckingAccount: Get>,
> TransactAsset
for NonFungiblesMutateAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>
{
fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungibles_adapter",
"can_check_in origin: {:?}, what: {:?}, context: {:?}",
_origin, what, context,
);
// Check we handle this asset.
let (class, instance) = Matcher::matches_nonfungibles(what)?;
match CheckAsset::asset_checking(&class) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::can_reduce_checked(class, instance),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance),
_ => Ok(()),
}
}
fn check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
log::trace!(
target: "xcm::fungibles_adapter",
"check_in origin: {:?}, what: {:?}, context: {:?}",
_origin, what, context,
);
if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
match CheckAsset::asset_checking(&class) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::reduce_checked(class, instance),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance),
_ => (),
}
}
}
fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungibles_adapter",
"can_check_out dest: {:?}, what: {:?}, context: {:?}",
_dest, what, context,
);
// Check we handle this asset.
let (class, instance) = Matcher::matches_nonfungibles(what)?;
match CheckAsset::asset_checking(&class) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::can_accrue_checked(class, instance),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance),
_ => Ok(()),
}
}
fn check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
log::trace!(
target: "xcm::fungibles_adapter",
"check_out dest: {:?}, what: {:?}, context: {:?}",
_dest, what, context,
);
if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
match CheckAsset::asset_checking(&class) {
// We track this asset's teleports to ensure no more come in than have gone out.
Some(MintLocation::Local) => Self::accrue_checked(class, instance),
// We track this asset's teleports to ensure no more go out than have come in.
Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance),
_ => (),
}
}
}
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungibles_adapter",
"deposit_asset what: {:?}, who: {:?}, context: {:?}",
what, who, context,
);
// Check we handle this asset.
let (class, instance) = Matcher::matches_nonfungibles(what)?;
let who = AccountIdConverter::convert_ref(who)
.map_err(|()| MatchError::AccountIdConversionFailed)?;
Assets::mint_into(&class, &instance, &who)
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))
}
fn withdraw_asset(
what: &MultiAsset,
who: &MultiLocation,
maybe_context: Option<&XcmContext>,
) -> result::Result {
log::trace!(
target: "xcm::fungibles_adapter",
"withdraw_asset what: {:?}, who: {:?}, maybe_context: {:?}",
what, who, maybe_context,
);
// Check we handle this asset.
let who = AccountIdConverter::convert_ref(who)
.map_err(|()| MatchError::AccountIdConversionFailed)?;
let (class, instance) = Matcher::matches_nonfungibles(what)?;
Assets::burn(&class, &instance, Some(&who))
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
Ok(what.clone().into())
}
}
pub struct NonFungiblesAdapter<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
impl<
Assets: nonfungibles::Mutate + nonfungibles::Transfer,
Matcher: MatchesNonFungibles,
AccountIdConverter: Convert,
AccountId: Clone + Eq, // can't get away without it since Currency is generic over it.
CheckAsset: AssetChecking,
CheckingAccount: Get>,
> TransactAsset
for NonFungiblesAdapter
{
fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
NonFungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::can_check_in(origin, what, context)
}
fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
NonFungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::check_in(origin, what, context)
}
fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
NonFungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::can_check_out(dest, what, context)
}
fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
NonFungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::check_out(dest, what, context)
}
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, context: &XcmContext) -> XcmResult {
NonFungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::deposit_asset(what, who, context)
}
fn withdraw_asset(
what: &MultiAsset,
who: &MultiLocation,
maybe_context: Option<&XcmContext>,
) -> result::Result {
NonFungiblesMutateAdapter::<
Assets,
Matcher,
AccountIdConverter,
AccountId,
CheckAsset,
CheckingAccount,
>::withdraw_asset(what, who, maybe_context)
}
fn transfer_asset(
what: &MultiAsset,
from: &MultiLocation,
to: &MultiLocation,
context: &XcmContext,
) -> result::Result {
NonFungiblesTransferAdapter::::transfer_asset(
what, from, to, context,
)
}
}