Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Check out/in assets when teleporting to maintain total issuance #3007

Merged
merged 8 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions runtime/kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,8 @@ parameter_types! {
/// Our XCM location ancestry - i.e. what, if anything, `Parent` means evaluated in our context. Since
/// Kusama is a top-level relay-chain, there is no ancestry.
pub const Ancestry: MultiLocation = MultiLocation::Null;
/// The check account, which holds any native assets that have been teleported out and not back in (yet).
pub CheckAccount: AccountId = XcmPallet::check_account();
}

/// The canonical means of converting a `MultiLocation` into an `AccountId`, used when we want to determine
Expand All @@ -1141,6 +1143,8 @@ pub type LocalAssetTransactor =
SovereignAccountOf,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
AccountId,
// We track our teleports in/out to keep total issuance correct.
CheckAccount,
>;

/// The means that we convert an the XCM message origin location into a local dispatch origin.
Expand Down
3 changes: 3 additions & 0 deletions runtime/rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ parameter_types! {
pub const RocLocation: MultiLocation = MultiLocation::Null;
pub const RococoNetwork: NetworkId = NetworkId::Polkadot;
pub const Ancestry: MultiLocation = MultiLocation::Null;
pub CheckAccount: AccountId = XcmPallet::check_account();
}

pub type SovereignAccountOf = (
Expand All @@ -574,6 +575,8 @@ pub type LocalAssetTransactor =
SovereignAccountOf,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
AccountId,
// It's a native asset so we keep track of the teleports to maintain total issuance.
CheckAccount,
>;

type LocalOriginConverter = (
Expand Down
3 changes: 3 additions & 0 deletions runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ parameter_types! {
pub const WndLocation: MultiLocation = MultiLocation::Null;
pub const Ancestry: MultiLocation = MultiLocation::Null;
pub WestendNetwork: NetworkId = NetworkId::Named(b"Westend".to_vec());
pub CheckAccount: AccountId = XcmPallet::check_account();
}

pub type LocationConverter = (
Expand All @@ -813,6 +814,8 @@ pub type LocalAssetTransactor =
LocationConverter,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
AccountId,
// It's a native asset so we keep track of the teleports to maintain total issuance.
CheckAccount,
>;

type LocalOriginConverter = (
Expand Down
7 changes: 7 additions & 0 deletions xcm/pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ use sp_runtime::{RuntimeDebug, traits::BadOrigin};
use frame_support::traits::{EnsureOrigin, OriginTrait, Filter, Get, Contains};

pub use pallet::*;
use frame_support::PalletId;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use xcm_executor::traits::WeightBounds;
use sp_runtime::traits::AccountIdConversion;

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
Expand Down Expand Up @@ -198,6 +200,11 @@ pub mod pallet {
};
T::XcmRouter::send_xcm(dest, message)
}

pub fn check_account() -> T::AccountId {
const ID: PalletId = PalletId(*b"py/xcmch");
AccountIdConversion::<T::AccountId>::into_account(&ID)
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions xcm/src/v0/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub enum Error {
LocationCannotHold,
/// The assets given to purchase weight is are insufficient for the weight desired.
TooExpensive,
/// The given asset is not handled.
AssetNotFound,
}

impl From<()> for Error {
Expand Down
41 changes: 36 additions & 5 deletions xcm/xcm-builder/src/currency_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

use sp_std::{result, convert::TryInto, marker::PhantomData};
use xcm::v0::{Error as XcmError, Result, MultiAsset, MultiLocation};
use sp_runtime::traits::SaturatedConversion;
use frame_support::traits::{ExistenceRequirement::AllowDeath, WithdrawReasons};
use sp_runtime::traits::{SaturatedConversion, CheckedSub};
use frame_support::traits::{ExistenceRequirement::AllowDeath, WithdrawReasons, Get};
use xcm_executor::traits::{MatchesFungible, Convert, TransactAsset};
use xcm_executor::Assets;

Expand All @@ -43,16 +43,47 @@ impl From<Error> for XcmError {
}
}

pub struct CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Currency, Matcher, AccountIdConverter, AccountId)>
pub struct CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>(
PhantomData<(Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount)>
);

impl<
Matcher: MatchesFungible<Currency::Balance>,
AccountIdConverter: Convert<MultiLocation, AccountId>,
Currency: frame_support::traits::Currency<AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
> TransactAsset for CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId> {
CheckedAccount: Get<Option<AccountId>>,
> TransactAsset for CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount> {
fn can_check_in(_origin: &MultiLocation, what: &MultiAsset) -> Result {
// Check we handle this asset.
let amount: Currency::Balance = Matcher::matches_fungible(what)
.ok_or(Error::AssetNotFound)?;
if let Some(checked_account) = CheckedAccount::get() {
let new_balance = Currency::free_balance(&checked_account)
.checked_sub(&amount)
.ok_or(XcmError::NotWithdrawable)?;
Currency::ensure_can_withdraw(&checked_account, amount, WithdrawReasons::TRANSFER, new_balance)
.map_err(|_| XcmError::NotWithdrawable)?;
}
Ok(())
}

fn check_in(_origin: &MultiLocation, what: &MultiAsset) {
if let Some(amount) = Matcher::matches_fungible(what) {
if let Some(checked_account) = CheckedAccount::get() {
let ok = Currency::withdraw(&checked_account, amount, WithdrawReasons::TRANSFER, AllowDeath).is_ok();
debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
}
}
}

fn check_out(_dest: &MultiLocation, what: &MultiAsset) {
if let Some(amount) = Matcher::matches_fungible(what) {
if let Some(checked_account) = CheckedAccount::get() {
Currency::deposit_creating(&checked_account, amount);
}
}
}

fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result {
// Check we handle this asset.
Expand Down
70 changes: 61 additions & 9 deletions xcm/xcm-builder/src/fungibles_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

use sp_std::{prelude::*, result, marker::PhantomData, borrow::Borrow};
use xcm::v0::{Error as XcmError, Result, MultiAsset, MultiLocation, Junction};
use frame_support::traits::{Get, tokens::fungibles};
use frame_support::traits::{Get, tokens::fungibles, Contains};
use xcm_executor::traits::{TransactAsset, Convert};

/// Asset transaction errors.
Expand Down Expand Up @@ -160,15 +160,49 @@ impl<
}
}

pub struct FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>
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: Convert<MultiLocation, AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
> TransactAsset for FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId> {
CheckAsset: Contains<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset for FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount> {
fn can_check_in(_origin: &MultiLocation, what: &MultiAsset) -> Result {
// Check we handle this asset.
let (asset_id, amount) = Matcher::matches_fungibles(what)?;
if CheckAsset::contains(&asset_id) {
// This is an asset whose teleports we track.
let checking_account = CheckingAccount::get();
Assets::can_withdraw(asset_id, &checking_account, amount)
.into_result()
.map_err(|_| XcmError::NotWithdrawable)?;
}
Ok(())
}

fn check_in(_origin: &MultiLocation, what: &MultiAsset) {
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
if CheckAsset::contains(&asset_id) {
let checking_account = CheckingAccount::get();
let ok = Assets::burn_from(asset_id, &checking_account, amount).is_ok();
debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
shawntabrizi marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

fn check_out(_dest: &MultiLocation, what: &MultiAsset) {
if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
if CheckAsset::contains(&asset_id) {
let checking_account = CheckingAccount::get();
let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok();
debug_assert!(ok, "`mint_into` cannot generally fail; qed");
}
}
}

fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result {
// Check we handle this asset.
Expand All @@ -193,25 +227,43 @@ impl<
}
}

pub struct FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>
pub struct FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>
);
impl<
Assets: fungibles::Mutate<AccountId> + fungibles::Transfer<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: Convert<MultiLocation, AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
> TransactAsset for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId> {
CheckAsset: Contains<Assets::AssetId>,
CheckingAccount: Get<AccountId>,
> TransactAsset for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount> {
fn can_check_in(origin: &MultiLocation, what: &MultiAsset) -> Result {
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
::can_check_in(origin, what)
}

fn check_in(origin: &MultiLocation, what: &MultiAsset) {
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
::check_in(origin, what)
}

fn check_out(dest: &MultiLocation, what: &MultiAsset) {
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
::check_out(dest, what)
}

fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result {
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::deposit_asset(what, who)
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
::deposit_asset(what, who)
}

fn withdraw_asset(
what: &MultiAsset,
who: &MultiLocation
) -> result::Result<xcm_executor::Assets, XcmError> {
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::withdraw_asset(what, who)
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
::withdraw_asset(what, who)
}

fn transfer_asset(
Expand Down
10 changes: 10 additions & 0 deletions xcm/xcm-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ impl<Config: config::Config> XcmExecutor<Config> {
// We only trust the origin to send us assets that they identify as their
// sovereign assets.
ensure!(Config::IsTeleporter::filter_asset_location(asset, &origin), XcmError::UntrustedTeleportLocation);
// We should check that the asset can actually be teleported in (for this to be in error, there
// would need to be an accounting violation by one of the trusted chains, so it's unlikely, but we
// don't want to punish a possibly innocent chain/user).
Config::AssetTransactor::can_check_in(&origin, asset)?;
}
for asset in assets.iter() {
Config::AssetTransactor::check_in(&origin, asset);
}
Some((Assets::from(assets), effects))
}
Expand Down Expand Up @@ -238,6 +245,9 @@ impl<Config: config::Config> XcmExecutor<Config> {
Config::XcmSender::send_xcm(reserve, Xcm::WithdrawAsset { assets, effects })?;
}
Order::InitiateTeleport { assets, dest, effects} => {
for asset in assets.iter() {
Config::AssetTransactor::check_out(&origin, asset);
}
let assets = Self::reanchored(holding.saturating_take(assets), &dest);
Config::XcmSender::send_xcm(dest, Xcm::TeleportAsset { assets, effects })?;
}
Expand Down
50 changes: 50 additions & 0 deletions xcm/xcm-executor/src/traits/transact_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,37 @@ use crate::Assets;
/// account locations such as a `MultiLocation::X1(Junction::Parachain)`. Different chains may handle them in
/// different ways.
pub trait TransactAsset {
/// Ensure that `teleport_in` will result in `Ok`.
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
///
/// When composed as a tuple, all type-items are called and at least one must result in `Ok`.
fn can_check_in(_origin: &MultiLocation, _what: &MultiAsset) -> XcmResult {
Err(XcmError::Unimplemented)
}

/// An asset has been teleported in from the given origin. This should do whatever housekeeping is needed.
///
/// Implementation note: In general this will do one of two things: On chains where the asset is native,
/// it will reduce the assets from a special "teleported" account so that a) total-issuance is preserved;
/// and b) to ensure that no more assets can be teleported in than were teleported out overall (this should
/// not be needed if the teleporting chains are to be trusted, but better to be safe than sorry). On chains
/// where the asset is not native then it will generally just be a no-op.
///
/// When composed as a tuple, all type-items are called. It is up to the implementor that there exists no
/// value for `_what` which can cause side-effects for more than one of the type-items.
fn check_in(_origin: &MultiLocation, _what: &MultiAsset) {}

/// An asset has been teleported out to the given destination. This should do whatever housekeeping is needed.
///
/// Implementation note: In general this will do one of two things: On chains where the asset is native,
/// it will increase the assets in a special "teleported" account so that a) total-issuance is preserved; and
/// b) to ensure that no more assets can be teleported in than were teleported out overall (this should not
/// be needed if the teleporting chains are to be trusted, but better to be safe than sorry). On chains where
/// the asset is not native then it will generally just be a no-op.
///
/// When composed as a tuple, all type-items are called. It is up to the implementor that there exists no
/// value for `_what` which can cause side-effects for more than one of the type-items.
fn check_out(_origin: &MultiLocation, _what: &MultiAsset) {}

/// Deposit the `what` asset into the account of `who`.
///
/// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed.
Expand Down Expand Up @@ -64,6 +95,25 @@ pub trait TransactAsset {

#[impl_trait_for_tuples::impl_for_tuples(30)]
impl TransactAsset for Tuple {
fn can_check_in(origin: &MultiLocation, what: &MultiAsset) -> XcmResult {
for_tuples!( #(
match Tuple::can_check_in(origin, what) {
Err(XcmError::AssetNotFound) => (),
r => return r,
}
)* );
Err(XcmError::AssetNotFound)
}
fn check_in(origin: &MultiLocation, what: &MultiAsset) {
for_tuples!( #(
Tuple::check_in(origin, what);
)* );
}
fn check_out(dest: &MultiLocation, what: &MultiAsset) {
for_tuples!( #(
Tuple::check_out(dest, what);
)* );
}
fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> XcmResult {
for_tuples!( #(
match Tuple::deposit_asset(what, who) { o @ Ok(_) => return o, _ => () }
Expand Down