Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FungibleAdapter #2684

Merged
merged 13 commits into from
Dec 14, 2023
317 changes: 317 additions & 0 deletions polkadot/xcm/xcm-builder/src/fungible_adapter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
// 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 <http://www.gnu.org/licenses/>.

//! Adapters to work with [`frame_support::traits::fungible`] through XCM.

use super::MintLocation;
use frame_support::traits::{
tokens::{
fungible, Fortitude::Polite, Precision::Exact, Preservation::Preserve, Provenance::Minted,
},
Get,
};
use sp_std::{marker::PhantomData, prelude::*, result};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesFungible, TransactAsset};

/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
/// handling an asset in the XCM executor.
/// Only works for transfers.
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: &MultiAsset,
from: &MultiLocation,
to: &MultiLocation,
_context: &XcmContext,
) -> result::Result<xcm_executor::Assets, XcmError> {
log::trace!(
target: "xcm::fungible_adapter",
"internal_transfer_asset what: {:?}, from: {:?}, to: {:?}",
what, from, to
);
// Check we handle the asset
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())
}
}

/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
/// handling an asset in the XCM executor.
/// Works for everything but transfers.
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, 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: &MultiLocation,
what: &MultiAsset,
_context: &XcmContext,
) -> XcmResult {
log::trace!(
target: "xcm::fungible_adapter",
"can_check_in origin: {:?}, what: {:?}",
_origin, what
);
// Check we handle this asset
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: &MultiLocation, what: &MultiAsset, _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: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> XcmResult {
log::trace!(
target: "xcm::fungible_adapter",
"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: &MultiLocation, what: &MultiAsset, _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: &MultiAsset,
who: &MultiLocation,
_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: &MultiAsset,
who: &MultiLocation,
_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::Assets, XcmError> {
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::burn_from(&who, amount, Exact, Polite)
.map_err(|error| XcmError::FailedToTransactAsset(error.into()))?;
Ok(what.clone().into())
}
}

/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for
/// handling an asset in the XCM executor.
/// Works for everything, transfers and teleport bookkeeping.
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: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_in(origin, what, context)
}

fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_in(origin, what, context)
}

fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::can_check_out(dest, what, context)
}

fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::check_out(dest, what, context)
}

fn deposit_asset(
what: &MultiAsset,
who: &MultiLocation,
context: Option<&XcmContext>,
) -> XcmResult {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::deposit_asset(what, who, context)
}

fn withdraw_asset(
what: &MultiAsset,
who: &MultiLocation,
maybe_context: Option<&XcmContext>,
) -> result::Result<xcm_executor::Assets, XcmError> {
FungibleMutateAdapter::<
Fungible,
Matcher,
AccountIdConverter,
AccountId,
CheckingAccount,
>::withdraw_asset(what, who, maybe_context)
}

fn internal_transfer_asset(
what: &MultiAsset,
from: &MultiLocation,
to: &MultiLocation,
context: &XcmContext,
) -> result::Result<xcm_executor::Assets, XcmError> {
FungibleTransferAdapter::<Fungible, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
what, from, to, context
)
}
}
3 changes: 3 additions & 0 deletions polkadot/xcm/xcm-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ pub use fee_handling::{
deposit_or_burn_fee, HandleFee, XcmFeeManagerFromComponents, XcmFeeToAccount,
};

mod fungible_adapter;
pub use fungible_adapter::{FungibleAdapter, FungibleMutateAdapter, FungibleTransferAdapter};

mod fungibles_adapter;
pub use fungibles_adapter::{
AssetChecking, DualMint, FungiblesAdapter, FungiblesMutateAdapter, FungiblesTransferAdapter,
Expand Down
9 changes: 4 additions & 5 deletions polkadot/xcm/xcm-simulator/example/src/parachain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ use polkadot_parachain_primitives::primitives::{
use xcm::{latest::prelude::*, VersionedXcm};
use xcm_builder::{
Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, ConvertedConcreteId,
CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds,
IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset,
SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
SovereignSignedViaLocation,
EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FungibleAdapter, IsConcrete,
NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset, SiblingParachainConvertsVia,
SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation,
};
use xcm_executor::{
traits::{ConvertLocation, JustTry},
Expand Down Expand Up @@ -202,7 +201,7 @@ parameter_types! {
}

pub type LocalAssetTransactor = (
XcmCurrencyAdapter<Balances, IsConcrete<KsmLocation>, LocationToAccountId, AccountId, ()>,
FungibleAdapter<Balances, IsConcrete<KsmLocation>, LocationToAccountId, AccountId, ()>,
NonFungiblesAdapter<
ForeignUniques,
ConvertedConcreteId<MultiLocation, AssetInstance, JustTry, JustTry>,
Expand Down
8 changes: 4 additions & 4 deletions polkadot/xcm/xcm-simulator/example/src/relay_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ use xcm::latest::prelude::*;
use xcm_builder::{
Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex,
ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser,
ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible,
FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative,
SignedToAccountId32, SovereignSignedViaLocation,
ConvertedConcreteId, FixedRateOfFungible, FixedWeightBounds, FungibleAdapter, IsConcrete,
NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, SignedToAccountId32,
SovereignSignedViaLocation,
};
use xcm_executor::{traits::JustTry, Config, XcmExecutor};

Expand Down Expand Up @@ -141,7 +141,7 @@ pub type LocationToAccountId = (
);

pub type LocalAssetTransactor = (
XcmCurrencyAdapter<Balances, IsConcrete<TokenLocation>, LocationToAccountId, AccountId, ()>,
franciscoaguirre marked this conversation as resolved.
Show resolved Hide resolved
FungibleAdapter<Balances, IsConcrete<TokenLocation>, LocationToAccountId, AccountId, ()>,
NonFungiblesAdapter<
Uniques,
ConvertedConcreteId<u32, u32, AsPrefixedGeneralIndex<(), u32, JustTry>, JustTry>,
Expand Down
14 changes: 14 additions & 0 deletions prdoc/pr_2684.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
title: Add XCM FungibleAdapter

doc:
- audience: Runtime Dev
description: |
A new AssetTransactor has been added to xcm-builder: FungibleAdapter.
It's meant to be used instead of the old CurrencyAdapter for configuring the XCM executor
to handle only one asset.

crates:
- name: "xcm-builder"

migrations: []
host_functions: []
Loading