mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 18:07:58 +00:00
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 <shawntabrizi@gmail.com> * Docs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user