Unverified Commit a458df1a authored by Gavin Wood's avatar Gavin Wood Committed by GitHub
Browse files

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



* Check out/in assets when teleporting to maintain total issuance

* Fixes

* Fixes

* Fixes

* Fixes

* Update xcm/xcm-executor/src/traits/transact_asset.rs
Co-authored-by: Shawn Tabrizi's avatarShawn Tabrizi <shawntabrizi@gmail.com>

* Docs
Co-authored-by: Shawn Tabrizi's avatarShawn Tabrizi <shawntabrizi@gmail.com>
parent 9f001da9
Pipeline #137824 passed with stages
in 27 minutes and 35 seconds
......@@ -1164,6 +1164,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
......@@ -1189,6 +1191,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.
......
......@@ -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 = (
......@@ -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 = (
......
......@@ -839,6 +839,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 = (
......@@ -856,6 +857,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 = (
......
......@@ -26,6 +26,7 @@ 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 {
......@@ -33,6 +34,7 @@ pub mod pallet {
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)]
......@@ -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)
}
}
}
......
......@@ -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 {
......
......@@ -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;
......@@ -43,8 +43,8 @@ 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<
......@@ -52,7 +52,38 @@ impl<
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.
......
......@@ -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.
......@@ -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");
}
}
}
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.
......@@ -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(
......
......@@ -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))
}
......@@ -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 })?;
}
......
......@@ -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.
......@@ -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, _ => () }
......@@ -83,4 +136,3 @@ impl TransactAsset for Tuple {
Err(XcmError::Unimplemented)
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment