diff --git a/runtime/kusama/src/xcm_config.rs b/runtime/kusama/src/xcm_config.rs index 59e32f2ca544..01bceb2f8e45 100644 --- a/runtime/kusama/src/xcm_config.rs +++ b/runtime/kusama/src/xcm_config.rs @@ -39,10 +39,10 @@ use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, IsChildSystemParachain, IsConcrete, MintLocation, - OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + CurrencyAdapter as XcmCurrencyAdapter, FrameTransactionalProcessor, IsChildSystemParachain, + IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::traits::WithOriginFilter; @@ -346,6 +346,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } parameter_types! { diff --git a/runtime/polkadot/src/xcm_config.rs b/runtime/polkadot/src/xcm_config.rs index 867253ea0346..2afeaef99ecf 100644 --- a/runtime/polkadot/src/xcm_config.rs +++ b/runtime/polkadot/src/xcm_config.rs @@ -42,9 +42,9 @@ use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, CurrencyAdapter as XcmCurrencyAdapter, - IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, - WeightInfoBounds, WithComputedOrigin, + FrameTransactionalProcessor, IsConcrete, MintLocation, OriginToPluralityVoice, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + UsingComponents, WeightInfoBounds, WithComputedOrigin, }; use xcm_executor::traits::WithOriginFilter; @@ -347,6 +347,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } parameter_types! { diff --git a/runtime/rococo/src/xcm_config.rs b/runtime/rococo/src/xcm_config.rs index 714a4f69e759..7916151b7c72 100644 --- a/runtime/rococo/src/xcm_config.rs +++ b/runtime/rococo/src/xcm_config.rs @@ -38,10 +38,10 @@ use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, BackingToPlurality, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, IsChildSystemParachain, IsConcrete, - MintLocation, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, - WithUniqueTopic, + CurrencyAdapter as XcmCurrencyAdapter, FixedWeightBounds, FrameTransactionalProcessor, + IsChildSystemParachain, IsConcrete, MintLocation, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -320,6 +320,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } parameter_types! { diff --git a/runtime/test-runtime/src/xcm_config.rs b/runtime/test-runtime/src/xcm_config.rs index 21ce8c877dc3..cd13e9585fc0 100644 --- a/runtime/test-runtime/src/xcm_config.rs +++ b/runtime/test-runtime/src/xcm_config.rs @@ -22,8 +22,8 @@ use frame_support::{ use frame_system::EnsureRoot; use xcm::latest::prelude::*; use xcm_builder::{ - AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, SignedAccountId32AsNative, - SignedToAccountId32, + AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, + SignedAccountId32AsNative, SignedToAccountId32, }; use xcm_executor::{ traits::{TransactAsset, WeightTrader}, @@ -74,6 +74,7 @@ impl TransactAsset for DummyAssetTransactor { } } +#[derive(Clone)] pub struct DummyWeightTrader; impl WeightTrader for DummyWeightTrader { fn new() -> Self { @@ -121,6 +122,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = super::RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } #[cfg(feature = "runtime-benchmarks")] diff --git a/runtime/westend/src/xcm_config.rs b/runtime/westend/src/xcm_config.rs index d6a3feb3bc0f..b294d8752605 100644 --- a/runtime/westend/src/xcm_config.rs +++ b/runtime/westend/src/xcm_config.rs @@ -37,9 +37,10 @@ use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, IsChildSystemParachain, IsConcrete, MintLocation, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + CurrencyAdapter as XcmCurrencyAdapter, FrameTransactionalProcessor, IsChildSystemParachain, + IsConcrete, MintLocation, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -269,6 +270,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location diff --git a/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index f5759afc0646..57d2fb6bb61c 100644 --- a/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -26,7 +26,7 @@ use frame_support::{ use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use xcm::latest::prelude::*; -use xcm_builder::{AllowUnpaidExecutionFrom, MintLocation}; +use xcm_builder::{AllowUnpaidExecutionFrom, FrameTransactionalProcessor, MintLocation}; type Block = frame_system::mocking::MockBlock; @@ -152,6 +152,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } impl crate::Config for Test { diff --git a/xcm/pallet-xcm-benchmarks/src/generic/mock.rs b/xcm/pallet-xcm-benchmarks/src/generic/mock.rs index 1b244f316de9..9f9c8b81d305 100644 --- a/xcm/pallet-xcm-benchmarks/src/generic/mock.rs +++ b/xcm/pallet-xcm-benchmarks/src/generic/mock.rs @@ -30,7 +30,7 @@ use xcm_builder::{ Assets, TestAssetExchanger, TestAssetLocker, TestAssetTrap, TestSubscriptionService, TestUniversalAliases, }, - AliasForeignAccountId32, AllowUnpaidExecutionFrom, + AliasForeignAccountId32, AllowUnpaidExecutionFrom, FrameTransactionalProcessor, }; use xcm_executor::traits::ConvertOrigin; @@ -131,6 +131,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Aliasers; + type TransactionalProcessor = FrameTransactionalProcessor; } impl crate::Config for Test { diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index b56b1af82def..10e1f9f0d7f2 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -31,8 +31,8 @@ use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, - FixedWeightBounds, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, + FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, }; use xcm_executor::XcmExecutor; @@ -306,6 +306,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } pub type LocalOriginToLocation = SignedToAccountId32; diff --git a/xcm/xcm-builder/src/lib.rs b/xcm/xcm-builder/src/lib.rs index e3f910409638..e2c89caff150 100644 --- a/xcm/xcm-builder/src/lib.rs +++ b/xcm/xcm-builder/src/lib.rs @@ -107,3 +107,6 @@ pub use origin_aliases::AliasForeignAccountId32; mod pay; pub use pay::{FixedLocation, LocatableAssetId, PayAccountId32OnChainOverXcm, PayOverXcm}; + +mod transactional; +pub use transactional::FrameTransactionalProcessor; diff --git a/xcm/xcm-builder/src/tests/mock.rs b/xcm/xcm-builder/src/tests/mock.rs index 66a676369a67..691010713263 100644 --- a/xcm/xcm-builder/src/tests/mock.rs +++ b/xcm/xcm-builder/src/tests/mock.rs @@ -21,7 +21,7 @@ use crate::{ pub use crate::{ AliasForeignAccountId32, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, FixedRateOfFungible, - FixedWeightBounds, TakeWeightCredit, + FixedWeightBounds, FrameTransactionalProcessor, TakeWeightCredit, }; use frame_support::traits::{ContainsPair, Everything}; pub use frame_support::{ @@ -685,6 +685,7 @@ impl Config for TestConfig { type CallDispatcher = TestCall; type SafeCallFilter = Everything; type Aliasers = AliasForeignAccountId32; + type TransactionalProcessor = (); } pub fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset { diff --git a/xcm/xcm-builder/src/transactional.rs b/xcm/xcm-builder/src/transactional.rs new file mode 100644 index 000000000000..025a1fa8170b --- /dev/null +++ b/xcm/xcm-builder/src/transactional.rs @@ -0,0 +1,39 @@ +// Copyright 2021 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 . + +use frame_support::storage::{with_transaction_unchecked, TransactionOutcome}; +use xcm::latest::prelude::*; +use xcm_executor::traits::ProcessTransaction; + +/// Transactional processor implementation using frame transactional layers. +pub struct FrameTransactionalProcessor; +impl ProcessTransaction for FrameTransactionalProcessor { + const IS_TRANSACTIONAL: bool = true; + + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>, + { + // Is safe to use because of the `MAX_XCM_DECODE_DEPTH`. + with_transaction_unchecked(|| -> TransactionOutcome> { + let outcome = f(); + match &outcome { + Ok(_) => TransactionOutcome::Commit(Ok(())), + Err(err) => TransactionOutcome::Rollback(Err(*err)), + } + }) + } +} diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 73cba6cb557b..5b7777b8cfe0 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -190,6 +190,16 @@ impl, R: TakeRevenue> Drop for FixedRateOfFungible } } +impl Clone for FixedRateOfFungible +where + T: Get<(AssetId, u128, u128)>, + R: TakeRevenue, +{ + fn clone(&self) -> FixedRateOfFungible { + Self(self.0, self.1, PhantomData) + } +} + /// Weight trader which uses the configured `WeightToFee` to set the right price for weight and then /// places any weight bought into the right account. pub struct UsingComponents< @@ -257,3 +267,16 @@ impl< OnUnbalanced::on_unbalanced(Currency::issue(self.1)); } } + +impl Clone + for UsingComponents +where + WeightToFee: WeightToFeeT, + AssetId: Get, + Currency: CurrencyT, + OnUnbalanced: OnUnbalancedT, +{ + fn clone(&self) -> UsingComponents { + Self(self.0, self.1, PhantomData) + } +} diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs index f799be7e4013..ab8efbb96177 100644 --- a/xcm/xcm-builder/tests/mock/mod.rs +++ b/xcm/xcm-builder/tests/mock/mod.rs @@ -34,8 +34,9 @@ use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, - IsChildSystemParachain, IsConcrete, MintLocation, RespectSuspension, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + FrameTransactionalProcessor, IsChildSystemParachain, IsConcrete, MintLocation, + RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + TakeWeightCredit, }; pub type AccountId = AccountId32; @@ -203,6 +204,7 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } pub type LocalOriginToLocation = SignedToAccountId32; diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs index 1fc5cef39215..fa0737c9d520 100644 --- a/xcm/xcm-executor/src/config.rs +++ b/xcm/xcm-executor/src/config.rs @@ -16,8 +16,8 @@ use crate::traits::{ AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, - FeeManager, OnResponse, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, - WeightTrader, + FeeManager, OnResponse, ProcessTransaction, ShouldExecute, TransactAsset, + VersionChangeNotifier, WeightBounds, WeightTrader, }; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, Parameter, PostDispatchInfo}, @@ -59,7 +59,7 @@ pub trait Config { type Weigher: WeightBounds; /// The means of purchasing weight credit for XCM execution. - type Trader: WeightTrader; + type Trader: WeightTrader + Clone; /// What to do when a response of a query is found. type ResponseHandler: OnResponse; @@ -110,4 +110,7 @@ pub trait Config { /// Use this type to explicitly whitelist calls that cannot undergo recursion. This is a /// temporary measure until we properly account for proof size weights for XCM instructions. type SafeCallFilter: Contains; + + /// Transactional processor for XCM instructions. + type TransactionalProcessor: ProcessTransaction; } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 57ddc4322923..e85c39e047ba 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -31,8 +31,8 @@ use xcm::latest::prelude::*; pub mod traits; use traits::{ validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, - DropAssets, Enact, ExportXcm, FeeManager, FeeReason, OnResponse, Properties, ShouldExecute, - TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + DropAssets, Enact, ExportXcm, FeeManager, FeeReason, OnResponse, ProcessTransaction, + Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, }; mod assets; @@ -476,73 +476,110 @@ impl XcmExecutor { ); match instr { WithdrawAsset(assets) => { - // Take `assets` from the origin account (on-chain) and place in holding. - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.into_inner().into_iter() { - Config::AssetTransactor::withdraw_asset(&asset, &origin, Some(&self.context))?; - self.subsume_asset(asset)?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + // Take `assets` from the origin account (on-chain) and place in holding. + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + + for asset in assets.into_inner().into_iter() { + Config::AssetTransactor::withdraw_asset( + &asset, + &origin, + Some(&self.context), + )?; + self.subsume_asset(asset)?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - Ok(()) + result }, ReserveAssetDeposited(assets) => { - // check whether we trust origin to be our reserve location for this asset. - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.into_inner().into_iter() { - // Must ensure that we recognise the asset as being managed by the origin. - ensure!( - Config::IsReserve::contains(&asset, &origin), - XcmError::UntrustedReserveLocation - ); - self.subsume_asset(asset)?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + // check whether we trust origin to be our reserve location for this asset. + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.into_inner().into_iter() { + // Must ensure that we recognise the asset as being managed by the origin. + ensure!( + Config::IsReserve::contains(&asset, &origin), + XcmError::UntrustedReserveLocation + ); + self.subsume_asset(asset)?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - Ok(()) + result }, TransferAsset { assets, beneficiary } => { - // Take `assets` from the origin account (on-chain) and place into dest account. - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.inner() { - Config::AssetTransactor::transfer_asset( - &asset, - origin, - &beneficiary, - &self.context, - )?; - } - Ok(()) + Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + // Take `assets` from the origin account (on-chain) and place into dest account. + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset( + &asset, + origin, + &beneficiary, + &self.context, + )?; + } + Ok(()) + }) }, TransferReserveAsset { mut assets, dest, xcm } => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - // Take `assets` from the origin account (on-chain) and place into dest account. - for asset in assets.inner() { - Config::AssetTransactor::transfer_asset(asset, origin, &dest, &self.context)?; - } - let reanchor_context = Config::UniversalLocation::get(); - assets.reanchor(&dest, reanchor_context).map_err(|()| XcmError::LocationFull)?; - let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?; - Ok(()) + Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + // Take `assets` from the origin account (on-chain) and place into dest account. + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset( + asset, + origin, + &dest, + &self.context, + )?; + } + let reanchor_context = Config::UniversalLocation::get(); + assets + .reanchor(&dest, reanchor_context) + .map_err(|()| XcmError::LocationFull)?; + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?; + Ok(()) + }) }, ReceiveTeleportedAsset(assets) => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - // check whether we trust origin to teleport this asset to us via config trait. - for asset in assets.inner() { - // We only trust the origin to send us assets that they identify as their - // sovereign assets. - ensure!( - Config::IsTeleporter::contains(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, &self.context)?; - } - for asset in assets.into_inner().into_iter() { - Config::AssetTransactor::check_in(&origin, &asset, &self.context); - self.subsume_asset(asset)?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + // check whether we trust origin to teleport this asset to us via config trait. + for asset in assets.inner() { + // We only trust the origin to send us assets that they identify as their + // sovereign assets. + ensure!( + Config::IsTeleporter::contains(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, &self.context)?; + } + for asset in assets.into_inner().into_iter() { + Config::AssetTransactor::check_in(&origin, &asset, &self.context); + self.subsume_asset(asset)?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - Ok(()) + result }, Transact { origin_kind, require_weight_at_most, mut call } => { // We assume that the Relay-chain is allowed to use transact on this parachain. @@ -614,24 +651,42 @@ impl XcmExecutor { Ok(()) }, DepositAsset { assets, beneficiary } => { - let deposited = self.holding.saturating_take(assets); - for asset in deposited.into_assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &beneficiary, &self.context)?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let deposited = self.holding.saturating_take(assets); + for asset in deposited.into_assets_iter() { + Config::AssetTransactor::deposit_asset( + &asset, + &beneficiary, + &self.context, + )?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - Ok(()) + result }, DepositReserveAsset { assets, dest, xcm } => { - let deposited = self.holding.saturating_take(assets); - for asset in deposited.assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &dest, &self.context)?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let deposited = self.holding.saturating_take(assets); + for asset in deposited.assets_iter() { + Config::AssetTransactor::deposit_asset(&asset, &dest, &self.context)?; + } + // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot + // be reanchored because we have already called `deposit_asset` on all assets. + let assets = Self::reanchored(deposited, &dest, None); + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot - // be reanchored because we have already called `deposit_asset` on all assets. - let assets = Self::reanchored(deposited, &dest, None); - let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; - Ok(()) + result }, InitiateReserveWithdraw { assets, reserve, xcm } => { // Note that here we are able to place any assets which could not be reanchored @@ -647,25 +702,32 @@ impl XcmExecutor { Ok(()) }, InitiateTeleport { assets, dest, xcm } => { - // We must do this first in order to resolve wildcards. - let assets = self.holding.saturating_take(assets); - for asset in assets.assets_iter() { - // We should check that the asset can actually be teleported out (for this to - // be in error, there would need to be an accounting violation by ourselves, - // so it's unlikely, but we don't want to allow that kind of bug to leak into - // a trusted chain. - Config::AssetTransactor::can_check_out(&dest, &asset, &self.context)?; - } - for asset in assets.assets_iter() { - Config::AssetTransactor::check_out(&dest, &asset, &self.context); + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + // We must do this first in order to resolve wildcards. + let assets = self.holding.saturating_take(assets); + for asset in assets.assets_iter() { + // We should check that the asset can actually be teleported out (for this to + // be in error, there would need to be an accounting violation by ourselves, + // so it's unlikely, but we don't want to allow that kind of bug to leak into + // a trusted chain. + Config::AssetTransactor::can_check_out(&dest, &asset, &self.context)?; + } + for asset in assets.assets_iter() { + Config::AssetTransactor::check_out(&dest, &asset, &self.context); + } + // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot + // be reanchored because we have already checked all assets out. + let assets = Self::reanchored(assets, &dest, None); + let mut message = vec![ReceiveTeleportedAsset(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(dest, Xcm(message), FeeReason::InitiateTeleport)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot - // be reanchored because we have already checked all assets out. - let assets = Self::reanchored(assets, &dest, None); - let mut message = vec![ReceiveTeleportedAsset(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest, Xcm(message), FeeReason::InitiateTeleport)?; - Ok(()) + result }, ReportHolding { response_info, assets } => { // Note that we pass `None` as `maybe_failed_bin` since no assets were ever removed @@ -681,18 +743,29 @@ impl XcmExecutor { Ok(()) }, BuyExecution { fees, weight_limit } => { - // There is no need to buy any weight is `weight_limit` is `Unlimited` since it - // would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution - // and thus there is some other reason why it has been determined that this XCM - // should be executed. - if let Some(weight) = Option::::from(weight_limit) { - // pay for `weight` using up to `fees` of the holding register. - let max_fee = - self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; - let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?; - self.subsume_assets(unspent)?; + let old_holding = self.holding.clone(); + let old_trader = self.trader.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + // There is no need to buy any weight is `weight_limit` is `Unlimited` since it + // would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution + // and thus there is some other reason why it has been determined that this XCM + // should be executed. + if let Some(weight) = Option::::from(weight_limit) { + // pay for `weight` using up to `fees` of the holding register. + let max_fee = self + .holding + .try_take(fees.into()) + .map_err(|_| XcmError::NotHoldingFees)?; + let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?; + self.subsume_assets(unspent)?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + self.trader = old_trader; } - Ok(()) + result }, RefundSurplus => self.refund_surplus(), SetErrorHandler(mut handler) => { @@ -716,13 +789,21 @@ impl XcmExecutor { Ok(()) }, ClaimAsset { assets, ticket } => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context); - ensure!(ok, XcmError::UnknownClaim); - for asset in assets.into_inner().into_iter() { - self.subsume_asset(asset)?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + let ok = + Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context); + ensure!(ok, XcmError::UnknownClaim); + for asset in assets.into_inner().into_iter() { + self.subsume_asset(asset)?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - Ok(()) + result }, Trap(code) => Err(XcmError::Trap(code)), SubscribeVersion { query_id, max_response_weight } => { @@ -823,69 +904,92 @@ impl XcmExecutor { Ok(()) }, ExportMessage { network, destination, xcm } => { - // The actual message sent to the bridge for forwarding is prepended with `UniversalOrigin` - // and `DescendOrigin` in order to ensure that the message is executed with this Origin. - // - // Prepend the desired message with instructions which effectively rewrite the origin. - // - // This only works because the remote chain empowers the bridge - // to speak for the local network. - let origin = self.context.origin.ok_or(XcmError::BadOrigin)?; - let universal_source = Config::UniversalLocation::get() - .within_global(origin) - .map_err(|()| XcmError::Unanchored)?; - let hash = (self.origin_ref(), &destination).using_encoded(blake2_128); - let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); - // Hash identifies the lane on the exporter which we use. We use the pairwise - // combination of the origin and destination to ensure origin/destination pairs will - // generally have their own lanes. - let (ticket, fee) = validate_export::( - network, - channel, - universal_source, - destination, - xcm, - )?; - self.take_fee(fee, FeeReason::Export(network))?; - Config::MessageExporter::deliver(ticket)?; - Ok(()) + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + // The actual message sent to the bridge for forwarding is prepended with `UniversalOrigin` + // and `DescendOrigin` in order to ensure that the message is executed with this Origin. + // + // Prepend the desired message with instructions which effectively rewrite the origin. + // + // This only works because the remote chain empowers the bridge + // to speak for the local network. + let origin = self.context.origin.ok_or(XcmError::BadOrigin)?; + let universal_source = Config::UniversalLocation::get() + .within_global(origin) + .map_err(|()| XcmError::Unanchored)?; + let hash = (self.origin_ref(), &destination).using_encoded(blake2_128); + let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); + // Hash identifies the lane on the exporter which we use. We use the pairwise + // combination of the origin and destination to ensure origin/destination pairs will + // generally have their own lanes. + let (ticket, fee) = validate_export::( + network, + channel, + universal_source, + destination, + xcm, + )?; + self.take_fee(fee, FeeReason::Export(network))?; + Config::MessageExporter::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result }, LockAsset { asset, unlocker } => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; - let lock_ticket = Config::AssetLocker::prepare_lock(unlocker, asset, origin)?; - let owner = - origin.reanchored(&unlocker, context).map_err(|_| XcmError::ReanchorFailed)?; - let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); - let (ticket, price) = validate_send::(unlocker, msg)?; - self.take_fee(price, FeeReason::LockAsset)?; - lock_ticket.enact()?; - Config::XcmSender::deliver(ticket)?; - Ok(()) - }, - UnlockAsset { asset, target } => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?; - Ok(()) + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; + let lock_ticket = Config::AssetLocker::prepare_lock(unlocker, asset, origin)?; + let owner = origin + .reanchored(&unlocker, context) + .map_err(|_| XcmError::ReanchorFailed)?; + let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); + let (ticket, price) = validate_send::(unlocker, msg)?; + self.take_fee(price, FeeReason::LockAsset)?; + lock_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result }, + UnlockAsset { asset, target } => + Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?; + Ok(()) + }), NoteUnlockable { asset, owner } => { let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetLocker::note_unlockable(origin, asset, owner)?; Ok(()) }, RequestUnlock { asset, locker } => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0; - let remote_target = Self::try_reanchor_multilocation(origin, &locker)?.0; - let reduce_ticket = - Config::AssetLocker::prepare_reduce_unlockable(locker, asset, origin)?; - let msg = - Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]); - let (ticket, price) = validate_send::(locker, msg)?; - self.take_fee(price, FeeReason::RequestUnlock)?; - reduce_ticket.enact()?; - Config::XcmSender::deliver(ticket)?; - Ok(()) + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| -> Result<(), XcmError> { + let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0; + let remote_target = Self::try_reanchor_multilocation(origin, &locker)?.0; + let reduce_ticket = + Config::AssetLocker::prepare_reduce_unlockable(locker, asset, origin)?; + let msg = + Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]); + let (ticket, price) = validate_send::(locker, msg)?; + self.take_fee(price, FeeReason::RequestUnlock)?; + reduce_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result }, ExchangeAsset { give, want, maximal } => { let give = self.holding.saturating_take(give); diff --git a/xcm/xcm-executor/src/traits/mod.rs b/xcm/xcm-executor/src/traits/mod.rs index a9439968fa6c..a9ea82ae582a 100644 --- a/xcm/xcm-executor/src/traits/mod.rs +++ b/xcm/xcm-executor/src/traits/mod.rs @@ -37,6 +37,8 @@ pub use token_matching::{ }; mod on_response; pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChangeNotifier}; +mod process_transaction; +pub use process_transaction::ProcessTransaction; mod should_execute; pub use should_execute::{CheckSuspension, Properties, ShouldExecute}; mod transact_asset; @@ -50,8 +52,9 @@ pub mod prelude { pub use super::{ export_xcm, validate_export, AssetExchange, AssetLock, ClaimAssets, ConvertOrigin, DropAssets, Enact, Error, ExportXcm, FeeManager, FeeReason, LockError, MatchesFungible, - MatchesFungibles, MatchesNonFungible, MatchesNonFungibles, OnResponse, ShouldExecute, - TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, WithOriginFilter, + MatchesFungibles, MatchesNonFungible, MatchesNonFungibles, OnResponse, ProcessTransaction, + ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + WithOriginFilter, }; #[allow(deprecated)] pub use super::{Identity, JustTry}; diff --git a/xcm/xcm-executor/src/traits/process_transaction.rs b/xcm/xcm-executor/src/traits/process_transaction.rs new file mode 100644 index 000000000000..96789ccf3e9b --- /dev/null +++ b/xcm/xcm-executor/src/traits/process_transaction.rs @@ -0,0 +1,55 @@ +// Copyright 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 . + +use xcm::latest::prelude::*; +/// Provides mechanisms for transactional processing of XCM instructions. +/// +/// This trait defines the behavior required to process XCM instructions in a transactional +/// manner. Implementers of this trait can ensure that XCM instructions are executed +/// atomically, meaning they either fully succeed or fully fail without any partial effects. +/// +/// Implementers of this trait can also choose to not process XCM instructions transactionally. +/// This is useful for cases where the implementer is not able to provide transactional guarantees. +/// In this case the `IS_TRANSACTIONAL` constant should be set to `false`. +/// The `()` type implements this trait in a non-transactional manner. +pub trait ProcessTransaction { + /// Whether the processor (i.e. the type implementing this trait) is transactional. + const IS_TRANSACTIONAL: bool; + /// Processes an XCM instruction encapsulated within the provided closure. Responsible for + /// processing an XCM instruction transactionally. If the closure returns an error, any + /// changes made during its execution should be rolled back. In the case where the + /// implementer is not able to provide transactional guarantees, the closure should be + /// executed as is. + /// # Parameters + /// - `f`: A closure that encapsulates the XCM instruction to be processed. It should + /// return a `Result` indicating the success or failure of the instruction. + /// + /// # Returns + /// - A `Result` indicating the overall success or failure of the transactional process. + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>; +} + +impl ProcessTransaction for () { + const IS_TRANSACTIONAL: bool = false; + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>, + { + f() + } +} diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index 9c4cbe8fcb2d..5bbfc8c52178 100644 --- a/xcm/xcm-simulator/example/src/parachain.rs +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -41,8 +41,8 @@ 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, + FrameTransactionalProcessor, IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, + ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{ @@ -249,6 +249,7 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } #[frame_support::pallet] diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs index 6f0e92dc91b9..f554156fd0bb 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -37,8 +37,8 @@ use xcm_builder::{ Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, - FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, NoChecking, NonFungiblesAdapter, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -194,6 +194,7 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } pub type LocalOriginToLocation = SignedToAccountId32; diff --git a/xcm/xcm-simulator/fuzzer/src/parachain.rs b/xcm/xcm-simulator/fuzzer/src/parachain.rs index 26379ed8dc8e..d4a070335250 100644 --- a/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -39,9 +39,9 @@ use polkadot_parachain::primitives::{ use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter as XcmCurrencyAdapter, - EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, NativeAsset, - ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, + IsConcrete, NativeAsset, ParentIsPreset, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -162,6 +162,7 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } #[frame_support::pallet] diff --git a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index 32f3b8aa83fb..f4d47315055a 100644 --- a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -36,8 +36,9 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, + FrameTransactionalProcessor, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -158,6 +159,7 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; } pub type LocalOriginToLocation = SignedToAccountId32;