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 all 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
54 changes: 53 additions & 1 deletion xcm/xcm-executor/src/traits/transact_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,40 @@ 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 `check_in` will result in `Ok`.
///
/// 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.
///
/// NOTE: This will make only a best-effort at bookkeeping. The caller should ensure that `can_check_in` has
/// returned with `Ok` in order to guarantee that this operation proceeds properly.
///
/// 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 +98,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 All @@ -83,4 +136,3 @@ impl TransactAsset for Tuple {
Err(XcmError::Unimplemented)
}
}