diff --git a/Cargo.lock b/Cargo.lock index 8a7ba656af1253485ad7eb410e2861ccba9f2b0a..0dda623c14abbacfa0cd86dd311c7adb459c31d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -896,21 +896,27 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", "frame-support", + "frame-system", "pallet-asset-conversion", + "pallet-asset-tx-payment", "pallet-assets", "pallet-balances", "pallet-message-queue", + "pallet-transaction-payment", "pallet-treasury", "pallet-xcm", "parachains-common", "parity-scale-codec", "penpal-runtime", "polkadot-runtime-common", + "sp-core", + "sp-keyring", "sp-runtime", "staging-xcm", "staging-xcm-executor", "westend-runtime", "westend-system-emulated-network", + "xcm-fee-payment-runtime-api", ] [[package]] @@ -3930,6 +3936,7 @@ dependencies = [ "sp-trie", "sp-version", "staging-xcm", + "staging-xcm-builder", "trie-db", "trie-standardmap", ] @@ -5122,6 +5129,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -12145,6 +12165,7 @@ dependencies = [ "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "xcm-fee-payment-runtime-api", ] [[package]] @@ -22651,8 +22672,10 @@ dependencies = [ "sp-consensus-beefy", "sp-core", "sp-runtime", + "staging-xcm", "westend-runtime", "westend-runtime-constants", + "xcm-fee-payment-runtime-api", ] [[package]] @@ -23292,14 +23315,24 @@ dependencies = [ name = "xcm-fee-payment-runtime-api" version = "0.1.0" dependencies = [ + "env_logger 0.9.3", + "frame-executive", "frame-support", + "frame-system", + "log", + "pallet-assets", + "pallet-balances", + "pallet-xcm", "parity-scale-codec", "scale-info", "sp-api", + "sp-io", "sp-runtime", "sp-std 14.0.0", "sp-weights", "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index ece72ac8494b763035b7ebfead71e3e280bf4b5c..607394603466e6cb60d20dbefa4aa47580b54c42 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -37,8 +37,9 @@ use codec::Encode; use frame_support::traits::Get; use sp_core::H256; use sp_runtime::{FixedPointNumber, FixedU128, Saturating}; +use sp_std::vec::Vec; use xcm::prelude::*; -use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter}; +use xcm_builder::{ExporterFor, InspectMessageQueues, SovereignPaidRemoteExporter}; pub use pallet::*; pub use weights::WeightInfo; @@ -95,7 +96,7 @@ pub mod pallet { /// Origin of the sibling bridge hub that is allowed to report bridge status. type BridgeHubOrigin: EnsureOrigin<Self::RuntimeOrigin>; /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. - type ToBridgeHubSender: SendXcm; + type ToBridgeHubSender: SendXcm + InspectMessageQueues; /// Underlying channel with the sibling bridge hub. It must match the channel, used /// by the `Self::ToBridgeHubSender`. type WithBridgeHubChannel: XcmChannelStatusProvider; @@ -396,6 +397,12 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> { } } +impl<T: Config<I>, I: 'static> InspectMessageQueues for Pallet<T, I> { + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + ViaBridgeHubExporter::<T, I>::get_messages() + } +} + #[cfg(test)] mod tests { use super::*; @@ -635,4 +642,36 @@ mod tests { ); }); } + + #[test] + fn get_messages_works() { + run_test(|| { + assert_ok!(send_xcm::<XcmBridgeHubRouter>( + (Parent, Parent, GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)).into(), + vec![ClearOrigin].into() + )); + assert_eq!( + XcmBridgeHubRouter::get_messages(), + vec![( + VersionedLocation::V4((Parent, Parachain(1002)).into()), + vec![VersionedXcm::V4( + Xcm::builder() + .withdraw_asset((Parent, 1_002_000)) + .buy_execution((Parent, 1_002_000), Unlimited) + .set_appendix( + Xcm::builder_unsafe() + .deposit_asset(AllCounted(1), (Parent, Parachain(1000))) + .build() + ) + .export_message( + Kusama, + Parachain(1000), + Xcm::builder_unsafe().clear_origin().build() + ) + .build() + )], + ),], + ); + }); + } } diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs index 20c86d1da9a2e315c9465b7e8fe0633401a3a3d4..3e2c1bb369cb72439e53e492cf638a404caa99a3 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -19,14 +19,16 @@ use crate as pallet_xcm_bridge_hub_router; use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; +use codec::Encode; use frame_support::{ construct_runtime, derive_impl, parameter_types, traits::{Contains, Equals}, }; use frame_system::EnsureRoot; use sp_runtime::{traits::ConstU128, BuildStorage}; +use sp_std::cell::RefCell; use xcm::prelude::*; -use xcm_builder::{NetworkExportTable, NetworkExportTableItem}; +use xcm_builder::{InspectMessageQueues, NetworkExportTable, NetworkExportTableItem}; pub type AccountId = u64; type Block = frame_system::mocking::MockBlock<TestRuntime>; @@ -102,23 +104,46 @@ pub struct TestToBridgeHubSender; impl TestToBridgeHubSender { pub fn is_message_sent() -> bool { - frame_support::storage::unhashed::get_or_default(b"TestToBridgeHubSender.Sent") + !Self::get_messages().is_empty() } } +thread_local! { + pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = RefCell::new(Vec::new()); +} + impl SendXcm for TestToBridgeHubSender { - type Ticket = (); + type Ticket = (Location, Xcm<()>); fn validate( - _destination: &mut Option<Location>, - _message: &mut Option<Xcm<()>>, + destination: &mut Option<Location>, + message: &mut Option<Xcm<()>>, ) -> SendResult<Self::Ticket> { - Ok(((), (BridgeFeeAsset::get(), HRMP_FEE).into())) + let pair = (destination.take().unwrap(), message.take().unwrap()); + Ok((pair, (BridgeFeeAsset::get(), HRMP_FEE).into())) } - fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> { - frame_support::storage::unhashed::put(b"TestToBridgeHubSender.Sent", &true); - Ok([0u8; 32]) + fn deliver(pair: Self::Ticket) -> Result<XcmHash, SendError> { + let hash = fake_message_hash(&pair.1); + SENT_XCM.with(|q| q.borrow_mut().push(pair)); + Ok(hash) + } +} + +impl InspectMessageQueues for TestToBridgeHubSender { + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + SENT_XCM.with(|q| { + (*q.borrow()) + .clone() + .iter() + .map(|(location, message)| { + ( + VersionedLocation::V4(location.clone()), + vec![VersionedXcm::V4(message.clone())], + ) + }) + .collect() + }) } } @@ -146,3 +171,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub fn run_test<T>(test: impl FnOnce() -> T) -> T { new_test_ext().execute_with(test) } + +pub(crate) fn fake_message_hash<T>(message: &Xcm<T>) -> XcmHash { + message.using_encoded(sp_io::hashing::blake2_256) +} diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index cc2e8943caad529ec05d6daa82598dc4c210aeee..57e274db361de98533fd258c554abcff498060b3 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -38,6 +38,7 @@ polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default- polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false } polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false, optional = true } xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } # Cumulus cumulus-pallet-parachain-system-proc-macro = { path = "proc-macro", default-features = false } @@ -95,6 +96,7 @@ std = [ "sp-tracing/std", "sp-trie/std", "trie-db/std", + "xcm-builder/std", "xcm/std", ] @@ -109,6 +111,7 @@ runtime-benchmarks = [ "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 54a1def59600dff5dd8b1484691dcb18fff5ce63..c8e7d1bb30f7166d50ab9aed8d1dfd0526e8f36e 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -55,7 +55,8 @@ use sp_runtime::{ BoundedSlice, FixedU128, RuntimeDebug, Saturating, }; use sp_std::{cmp, collections::btree_map::BTreeMap, prelude::*}; -use xcm::latest::XcmHash; +use xcm::{latest::XcmHash, VersionedLocation, VersionedXcm}; +use xcm_builder::InspectMessageQueues; mod benchmarking; pub mod migration; @@ -1608,6 +1609,19 @@ impl<T: Config> UpwardMessageSender for Pallet<T> { } } +impl<T: Config> InspectMessageQueues for Pallet<T> { + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + use xcm::prelude::*; + + let messages: Vec<VersionedXcm<()>> = PendingUpwardMessages::<T>::get() + .iter() + .map(|encoded_message| VersionedXcm::<()>::decode(&mut &encoded_message[..]).unwrap()) + .collect(); + + vec![(VersionedLocation::V4(Parent.into()), messages)] + } +} + #[cfg(feature = "runtime-benchmarks")] impl<T: Config> polkadot_runtime_common::xcm_sender::EnsureForParachain for Pallet<T> { fn ensure(para_id: ParaId) { diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index ab196c6d3ec6a089d8099d6ae893631ac513e9d7..e3530ef7bf0e1e093ddb85e639a7a3ff757ad2da 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -28,6 +28,7 @@ polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-f polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false } xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } # Cumulus cumulus-primitives-core = { path = "../../primitives/core", default-features = false } @@ -46,9 +47,6 @@ sp-core = { path = "../../../substrate/primitives/core" } pallet-balances = { path = "../../../substrate/frame/balances" } frame-support = { path = "../../../substrate/frame/support", features = ["experimental"] } -# Polkadot -xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder" } - # Cumulus cumulus-pallet-parachain-system = { path = "../parachain-system", features = ["parameterized-consensus-hook"] } @@ -71,6 +69,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", "xcm-executor/std", "xcm/std", ] diff --git a/cumulus/pallets/xcmp-queue/src/lib.rs b/cumulus/pallets/xcmp-queue/src/lib.rs index 7de2fd8094213aaa0f8b8ef0dbb4539556b48465..cc785b66150e52731cdb03d2c41aaad0058d3ef9 100644 --- a/cumulus/pallets/xcmp-queue/src/lib.rs +++ b/cumulus/pallets/xcmp-queue/src/lib.rs @@ -70,7 +70,8 @@ use scale_info::TypeInfo; use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_runtime::{FixedU128, RuntimeDebug, Saturating}; use sp_std::prelude::*; -use xcm::{latest::prelude::*, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH}; +use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH}; +use xcm_builder::InspectMessageQueues; use xcm_executor::traits::ConvertOrigin; pub use pallet::*; @@ -947,6 +948,38 @@ impl<T: Config> SendXcm for Pallet<T> { } } +impl<T: Config> InspectMessageQueues for Pallet<T> { + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + use xcm::prelude::*; + + OutboundXcmpMessages::<T>::iter() + .map(|(para_id, _, messages)| { + let mut data = &messages[..]; + let decoded_format = + XcmpMessageFormat::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut data) + .unwrap(); + if decoded_format != XcmpMessageFormat::ConcatenatedVersionedXcm { + panic!("Unexpected format.") + } + let mut decoded_messages = Vec::new(); + while !data.is_empty() { + let decoded_message = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut data, + ) + .unwrap(); + decoded_messages.push(decoded_message); + } + + ( + VersionedLocation::V4((Parent, Parachain(para_id.into())).into()), + decoded_messages, + ) + }) + .collect() + } +} + impl<T: Config> FeeTracker for Pallet<T> { type Id = ParaId; diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 9d9a723cf8b538b604c45ff5b2bd366cd6f92c2d..e258576aa3f6dd0342f712498654ef102fb6f321 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -178,6 +178,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } pub type XcmRouter = ( diff --git a/cumulus/pallets/xcmp-queue/src/tests.rs b/cumulus/pallets/xcmp-queue/src/tests.rs index 0b41095828f2f93810a2855d175ecd60a12853d7..f48e9eec3ac05b85794a2f18fc279566ad613272 100644 --- a/cumulus/pallets/xcmp-queue/src/tests.rs +++ b/cumulus/pallets/xcmp-queue/src/tests.rs @@ -844,3 +844,43 @@ fn verify_fee_factor_increase_and_decrease() { assert!(DeliveryFeeFactor::<Test>::get(sibling_para_id) < FixedU128::from_float(1.63)); }); } + +#[test] +fn get_messages_works() { + new_test_ext().execute_with(|| { + use xcm_builder::InspectMessageQueues; + let sibling_para_id = ParaId::from(2001); + ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(sibling_para_id); + let destination: Location = (Parent, Parachain(sibling_para_id.into())).into(); + let other_sibling_para_id = ParaId::from(2002); + let other_destination: Location = (Parent, Parachain(other_sibling_para_id.into())).into(); + let message = Xcm(vec![ClearOrigin]); + assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), message.clone())); + assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), message.clone())); + assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), message.clone())); + ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(other_sibling_para_id); + assert_ok!(send_xcm::<XcmpQueue>(other_destination.clone(), message.clone())); + assert_ok!(send_xcm::<XcmpQueue>(other_destination.clone(), message)); + let queued_messages = XcmpQueue::get_messages(); + assert_eq!( + queued_messages, + vec![ + ( + VersionedLocation::V4(other_destination), + vec![ + VersionedXcm::V4(Xcm(vec![ClearOrigin])), + VersionedXcm::V4(Xcm(vec![ClearOrigin])), + ], + ), + ( + VersionedLocation::V4(destination), + vec![ + VersionedXcm::V4(Xcm(vec![ClearOrigin])), + VersionedXcm::V4(Xcm(vec![ClearOrigin])), + VersionedXcm::V4(Xcm(vec![ClearOrigin])), + ], + ), + ], + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml index 20aedb50e6a18ef1112ab23b2a6d05780b6b443f..e4688a1c9f022dd9aafd821ee39bfc5906a7b2a0 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml @@ -25,6 +25,8 @@ pallet-staking = { path = "../../../../../../../substrate/frame/staking", defaul polkadot-primitives = { path = "../../../../../../../polkadot/primitives", default-features = false } westend-runtime-constants = { path = "../../../../../../../polkadot/runtime/westend/constants", default-features = false } westend-runtime = { path = "../../../../../../../polkadot/runtime/westend" } +xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } +xcm-fee-payment-runtime-api = { path = "../../../../../../../polkadot/xcm/xcm-fee-payment-runtime-api", default-features = false } # Cumulus parachains-common = { path = "../../../../../common" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 00f4308324a96734afac879cafd1e51300407a26..0a2b0f6d45ee0480fe91392576068b1cafcb6cdf 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -16,18 +16,24 @@ assert_matches = "1.5.0" # Substrate sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } +sp-keyring = { path = "../../../../../../../substrate/primitives/keyring", default-features = false } +sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false } frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../../../substrate/frame/system", default-features = false } pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false } pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } pallet-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", default-features = false } pallet-treasury = { path = "../../../../../../../substrate/frame/treasury", default-features = false } pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue", default-features = false } +pallet-transaction-payment = { path = "../../../../../../../substrate/frame/transaction-payment", default-features = false } +pallet-asset-tx-payment = { path = "../../../../../../../substrate/frame/transaction-payment/asset-tx-payment", default-features = false } # Polkadot polkadot-runtime-common = { path = "../../../../../../../polkadot/runtime/common" } xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } +xcm-fee-payment-runtime-api = { path = "../../../../../../../polkadot/xcm/xcm-fee-payment-runtime-api", default-features = false } westend-runtime = { path = "../../../../../../../polkadot/runtime/westend" } # Cumulus diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index bf013697b4c750c8a9f3da3732e97386ba1cae74..61eb70524fc9ae2a23e5061400c57ca719bae4e1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -21,3 +21,4 @@ mod set_xcm_versions; mod swap; mod teleport; mod treasury; +mod xcm_fee_estimation; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs new file mode 100644 index 0000000000000000000000000000000000000000..aeec9b44dab4ce7648e609e13136c381a5a50695 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs @@ -0,0 +1,370 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests to ensure correct XCM fee estimation for cross-chain asset transfers. + +use crate::imports::*; + +use sp_keyring::AccountKeyring::Alice; +use sp_runtime::{generic, MultiSignature}; +use xcm_fee_payment_runtime_api::{ + dry_run::runtime_decl_for_xcm_dry_run_api::XcmDryRunApiV1, + fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV1, +}; + +/// We are able to dry-run and estimate the fees for a teleport between relay and system para. +/// Scenario: Alice on Westend relay chain wants to teleport WND to Asset Hub. +/// We want to know the fees using the `XcmDryRunApi` and `XcmPaymentApi`. +#[test] +fn teleport_relay_system_para_works() { + let destination: Location = Parachain(1000).into(); // Asset Hub. + let beneficiary_id = AssetHubWestendReceiver::get(); + let beneficiary: Location = AccountId32 { id: beneficiary_id.clone().into(), network: None } // Test doesn't allow specifying a network here. + .into(); // Beneficiary in Asset Hub. + let teleport_amount = 1_000_000_000_000; // One WND (12 decimals). + let assets: Assets = vec![(Here, teleport_amount).into()].into(); + + // We get them from the Westend closure. + let mut delivery_fees_amount = 0; + let mut remote_message = VersionedXcm::V4(Xcm(Vec::new())); + <Westend as TestExt>::new_ext().execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + + let call = RuntimeCall::XcmPallet(pallet_xcm::Call::transfer_assets { + dest: Box::new(VersionedLocation::V4(destination.clone())), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets)), + fee_asset_item: 0, + weight_limit: Unlimited, + }); + let sender = Alice; // Is the same as `WestendSender`. + let extrinsic = construct_extrinsic_westend(sender, call); + let result = Runtime::dry_run_extrinsic(extrinsic).unwrap(); + assert_eq!(result.forwarded_xcms.len(), 1); + let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + assert_eq!(messages_to_query.len(), 1); + remote_message = messages_to_query[0].clone(); + let delivery_fees = + Runtime::query_delivery_fees(destination_to_query.clone(), remote_message.clone()) + .unwrap(); + delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // This is set in the AssetHubWestend closure. + let mut remote_execution_fees = 0; + <AssetHubWestend as TestExt>::execute_with(|| { + type Runtime = <AssetHubWestend as Chain>::Runtime; + + let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap(); + remote_execution_fees = + Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Parent.into())) + .unwrap(); + }); + + let test_args = TestContext { + sender: WestendSender::get(), // Alice. + receiver: AssetHubWestendReceiver::get(), // Bob in Asset Hub. + args: TestArgs::new_relay(destination, beneficiary_id, teleport_amount), + }; + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + assert_eq!(sender_balance_before, 1_000_000_000_000_000_000); + assert_eq!(receiver_balance_before, 4_096_000_000_000); + + test.set_dispatchable::<Westend>(transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // We now know the exact fees. + assert_eq!( + sender_balance_after, + sender_balance_before - delivery_fees_amount - teleport_amount + ); + assert_eq!( + receiver_balance_after, + receiver_balance_before + teleport_amount - remote_execution_fees + ); +} + +/// We are able to dry-run and estimate the fees for a multi-hop XCM journey. +/// Scenario: Alice on PenpalA has some WND and wants to send them to PenpalB. +/// We want to know the fees using the `XcmDryRunApi` and `XcmPaymentApi`. +#[test] +fn multi_hop_works() { + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let amount_to_send = 1_000_000_000_000; // One WND (12 decimals). + let asset_owner = PenpalAssetOwner::get(); + let assets: Assets = (Parent, amount_to_send).into(); + let relay_native_asset_location = RelayLocation::get(); + let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id()); + let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay.clone()); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + <PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + + // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve + Westend::fund_accounts(vec![(sov_of_sender_on_relay.clone().into(), amount_to_send * 2)]); + + // Init values for Parachain Destination + let beneficiary_id = PenpalBReceiver::get(); + let beneficiary: Location = AccountId32 { + id: beneficiary_id.clone().into(), + network: None, // Test doesn't allow specifying a network here. + } + .into(); + + // We get them from the PenpalA closure. + let mut delivery_fees_amount = 0; + let mut remote_message = VersionedXcm::V4(Xcm(Vec::new())); + <PenpalA as TestExt>::execute_with(|| { + type Runtime = <PenpalA as Chain>::Runtime; + type RuntimeCall = <PenpalA as Chain>::RuntimeCall; + + let call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::transfer_assets { + dest: Box::new(VersionedLocation::V4(destination.clone())), + beneficiary: Box::new(VersionedLocation::V4(beneficiary)), + assets: Box::new(VersionedAssets::V4(assets.clone())), + fee_asset_item: 0, + weight_limit: Unlimited, + }); + let sender = Alice; // Same as `PenpalASender`. + let extrinsic = construct_extrinsic_penpal(sender, call); + let result = Runtime::dry_run_extrinsic(extrinsic).unwrap(); + assert_eq!(result.forwarded_xcms.len(), 1); + let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + assert_eq!(messages_to_query.len(), 1); + remote_message = messages_to_query[0].clone(); + let delivery_fees = + Runtime::query_delivery_fees(destination_to_query.clone(), remote_message.clone()) + .unwrap(); + delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // This is set in the Westend closure. + let mut intermediate_execution_fees = 0; + let mut intermediate_delivery_fees_amount = 0; + let mut intermediate_remote_message = VersionedXcm::V4(Xcm::<()>(Vec::new())); + <Westend as TestExt>::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + + // First we get the execution fees. + let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap(); + intermediate_execution_fees = + Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Here.into())).unwrap(); + + // We have to do this to turn `VersionedXcm<()>` into `VersionedXcm<RuntimeCall>`. + let xcm_program = + VersionedXcm::V4(Xcm::<RuntimeCall>::from(remote_message.clone().try_into().unwrap())); + + // Now we get the delivery fees to the final destination. + let result = + Runtime::dry_run_xcm(sender_as_seen_by_relay.clone().into(), xcm_program).unwrap(); + let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + // There's actually two messages here. + // One created when the message we sent from PenpalA arrived and was executed. + // The second one when we dry-run the xcm. + // We could've gotten the message from the queue without having to dry-run, but + // offchain applications would have to dry-run, so we do it here as well. + intermediate_remote_message = messages_to_query[0].clone(); + let delivery_fees = Runtime::query_delivery_fees( + destination_to_query.clone(), + intermediate_remote_message.clone(), + ) + .unwrap(); + intermediate_delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // Get the final execution fees in the destination. + let mut final_execution_fees = 0; + <PenpalB as TestExt>::execute_with(|| { + type Runtime = <PenpalB as Chain>::Runtime; + + let weight = Runtime::query_xcm_weight(intermediate_remote_message.clone()).unwrap(); + final_execution_fees = + Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Parent.into())) + .unwrap(); + }); + + // Dry-running is done. + PenpalA::reset_ext(); + Westend::reset_ext(); + PenpalB::reset_ext(); + + // Fund accounts again. + PenpalA::mint_foreign_asset( + <PenpalA as Chain>::RuntimeOrigin::signed(asset_owner), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + Westend::fund_accounts(vec![(sov_of_sender_on_relay.into(), amount_to_send * 2)]); + + // Actually run the extrinsic. + let test_args = TestContext { + sender: PenpalASender::get(), // Alice. + receiver: PenpalBReceiver::get(), // Bob in PenpalB. + args: TestArgs::new_para( + destination, + beneficiary_id.clone(), + amount_to_send, + assets, + None, + 0, + ), + }; + let mut test = ParaToParaThroughRelayTest::new(test_args); + + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &beneficiary_id) + }); + + test.set_dispatchable::<PenpalA>(transfer_assets_para_to_para); + test.assert(); + + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &beneficiary_id) + }); + + // We know the exact fees on every hop. + assert_eq!( + sender_assets_after, + sender_assets_before - amount_to_send - delivery_fees_amount /* This is charged directly + * from the sender's + * account. */ + ); + assert_eq!( + receiver_assets_after, + receiver_assets_before + amount_to_send - + intermediate_execution_fees - + intermediate_delivery_fees_amount - + final_execution_fees + ); +} + +fn get_amount_from_versioned_assets(assets: VersionedAssets) -> u128 { + let latest_assets: Assets = assets.try_into().unwrap(); + let Fungible(amount) = latest_assets.inner()[0].fun else { + unreachable!("asset is fungible"); + }; + amount +} + +fn transfer_assets(test: RelayToSystemParaTest) -> DispatchResult { + <Westend as WestendPallet>::XcmPallet::transfer_assets( + test.signed_origin, + bx!(test.args.dest.into()), + bx!(test.args.beneficiary.into()), + bx!(test.args.assets.into()), + test.args.fee_asset_item, + test.args.weight_limit, + ) +} + +fn transfer_assets_para_to_para(test: ParaToParaThroughRelayTest) -> DispatchResult { + <PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets( + test.signed_origin, + bx!(test.args.dest.into()), + bx!(test.args.beneficiary.into()), + bx!(test.args.assets.into()), + test.args.fee_asset_item, + test.args.weight_limit, + ) +} + +// Constructs the SignedExtra component of an extrinsic for the Westend runtime. +fn construct_extrinsic_westend( + sender: sp_keyring::AccountKeyring, + call: westend_runtime::RuntimeCall, +) -> westend_runtime::UncheckedExtrinsic { + type Runtime = <Westend as Chain>::Runtime; + let account_id = <Runtime as frame_system::Config>::AccountId::from(sender.public()); + let tip = 0; + let extra: westend_runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::<Runtime>::new(), + frame_system::CheckSpecVersion::<Runtime>::new(), + frame_system::CheckTxVersion::<Runtime>::new(), + frame_system::CheckGenesis::<Runtime>::new(), + frame_system::CheckMortality::<Runtime>::from(sp_runtime::generic::Era::immortal()), + frame_system::CheckNonce::<Runtime>::from( + frame_system::Pallet::<Runtime>::account(&account_id).nonce, + ), + frame_system::CheckWeight::<Runtime>::new(), + pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), + ); + let raw_payload = westend_runtime::SignedPayload::new(call, extra).unwrap(); + let signature = raw_payload.using_encoded(|payload| sender.sign(payload)); + let (call, extra, _) = raw_payload.deconstruct(); + westend_runtime::UncheckedExtrinsic::new_signed( + call, + account_id.into(), + MultiSignature::Sr25519(signature), + extra, + ) +} + +// Constructs the SignedExtra component of an extrinsic for the Westend runtime. +fn construct_extrinsic_penpal( + sender: sp_keyring::AccountKeyring, + call: penpal_runtime::RuntimeCall, +) -> penpal_runtime::UncheckedExtrinsic { + type Runtime = <PenpalA as Chain>::Runtime; + let account_id = <Runtime as frame_system::Config>::AccountId::from(sender.public()); + let tip = 0; + let extra: penpal_runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::<Runtime>::new(), + frame_system::CheckSpecVersion::<Runtime>::new(), + frame_system::CheckTxVersion::<Runtime>::new(), + frame_system::CheckGenesis::<Runtime>::new(), + frame_system::CheckEra::<Runtime>::from(generic::Era::immortal()), + frame_system::CheckNonce::<Runtime>::from( + frame_system::Pallet::<Runtime>::account(&account_id).nonce, + ), + frame_system::CheckWeight::<Runtime>::new(), + pallet_asset_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None), + ); + type SignedPayload = + generic::SignedPayload<penpal_runtime::RuntimeCall, penpal_runtime::SignedExtra>; + let raw_payload = SignedPayload::new(call, extra).unwrap(); + let signature = raw_payload.using_encoded(|payload| sender.sign(payload)); + let (call, extra, _) = raw_payload.deconstruct(); + penpal_runtime::UncheckedExtrinsic::new_signed( + call, + account_id.into(), + MultiSignature::Sr25519(signature), + extra, + ) +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 64abedbaac789060aaeada1a7bf236b77017301d..888193c5c6ea7e02e879428b920cd1ed70c15e54 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -138,6 +138,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 383751578e57e47ff50ebd25907203dd4c9412f0..f81a107fae0537095ece60c58592ca5e2b7068f7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -100,7 +100,10 @@ use xcm::{ latest::prelude::{AssetId, BodyId}, IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm, }; -use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; +use xcm_fee_payment_runtime_api::{ + dry_run::{Error as XcmDryRunApiError, ExtrinsicDryRunEffects, XcmDryRunEffects}, + fees::Error as XcmPaymentApiError, +}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -1281,7 +1284,7 @@ impl_runtime_apis! { } } - impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime { + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi<Block> for Runtime { fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> { let acceptable = vec![ // native token @@ -1320,6 +1323,70 @@ impl_runtime_apis! { } } + impl xcm_fee_payment_runtime_api::dry_run::XcmDryRunApi<Block, RuntimeCall, RuntimeEvent> for Runtime { + fn dry_run_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> Result<ExtrinsicDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm_executor::RecordXcm; + use xcm::prelude::*; + + pallet_xcm::Pallet::<Runtime>::set_record_xcm(true); + let result = Executive::apply_extrinsic(extrinsic).map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_extrinsic", + "Applying extrinsic failed with error {:?}", + error, + ); + XcmDryRunApiError::InvalidExtrinsic + })?; + let local_xcm = pallet_xcm::Pallet::<Runtime>::recorded_xcm(); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(ExtrinsicDryRunEffects { + local_xcm: local_xcm.map(VersionedXcm::<()>::V4), + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + + fn dry_run_xcm(origin_location: VersionedLocation, program: VersionedXcm<RuntimeCall>) -> Result<XcmDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm::prelude::*; + + let origin_location: Location = origin_location.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Location version conversion failed with error: {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let program: Xcm<RuntimeCall> = program.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Xcm version conversion failed with error {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let mut hash = program.using_encoded(sp_core::hashing::blake2_256); + let result = xcm_executor::XcmExecutor::<xcm_config::XcmConfig>::prepare_and_execute( + origin_location, + program, + &mut hash, + Weight::MAX, // Max limit available for execution. + Weight::zero(), + ); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(XcmDryRunEffects { + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + } + impl cumulus_primitives_core::CollectCollationInfo<Block> for Runtime { fn collect_collation_info(header: &<Block as BlockT>::Header) -> cumulus_primitives_core::CollationInfo { ParachainSystem::collect_collation_info(header) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index a73c1cc33ea0b958bf165fa54cd554548562f099..664d2b9c9dd593536404f02d895a414e6c232bfa 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -424,6 +424,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Converts a local signed origin into an XCM location. diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 3ba53eb3f9370c8bb56b364a9980145dd3a679a9..bacc9c1b7c29eba47f0ddc1140771407e7d1ab3c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -135,6 +135,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index e96ba3d962d87c7068dfbc75f8084dcdee4c6ee4..b5c3ed5053c4b77e1ecf0adeb8c99c5beecb81e0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -85,17 +85,24 @@ pub use sp_runtime::BuildStorage; use assets_common::{foreign_creators::ForeignCreators, matching::FromSiblingParachain}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use xcm::{ + prelude::{VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}, + IntoVersion, +}; + +// We exclude `Assets` since it's the name of a pallet +use xcm::latest::prelude::AssetId; #[cfg(feature = "runtime-benchmarks")] use xcm::latest::prelude::{ Asset, Fungible, Here, InteriorLocation, Junction, Junction::*, Location, NetworkId, NonFungible, Parent, ParentThen, Response, XCM_VERSION, }; -use xcm::{ - latest::prelude::AssetId, IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation, - VersionedXcm, + +use xcm_fee_payment_runtime_api::{ + dry_run::{Error as XcmDryRunApiError, ExtrinsicDryRunEffects, XcmDryRunEffects}, + fees::Error as XcmPaymentApiError, }; -use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -1311,6 +1318,109 @@ impl_runtime_apis! { } } + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi<Block> for Runtime { + fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> { + let acceptable = vec![ + // native token + VersionedAssetId::from(AssetId(xcm_config::WestendLocation::get())) + ]; + + Ok(acceptable + .into_iter() + .filter_map(|asset| asset.into_version(xcm_version).ok()) + .collect()) + } + + fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { + match asset.try_as::<AssetId>() { + Ok(asset_id) if asset_id.0 == xcm_config::WestendLocation::get() => { + // for native token + Ok(WeightToFee::weight_to_fee(&weight)) + }, + Ok(asset_id) => { + log::trace!(target: "xcm::xcm_fee_payment_runtime_api", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); + Err(XcmPaymentApiError::AssetNotFound) + }, + Err(_) => { + log::trace!(target: "xcm::xcm_fee_payment_runtime_api", "query_weight_to_asset_fee - failed to convert asset: {asset:?}!"); + Err(XcmPaymentApiError::VersionedConversionFailed) + } + } + } + + fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> { + PolkadotXcm::query_xcm_weight(message) + } + + fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, XcmPaymentApiError> { + PolkadotXcm::query_delivery_fees(destination, message) + } + } + + impl xcm_fee_payment_runtime_api::dry_run::XcmDryRunApi<Block, RuntimeCall, RuntimeEvent> for Runtime { + fn dry_run_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> Result<ExtrinsicDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm_executor::RecordXcm; + use xcm::prelude::*; + + pallet_xcm::Pallet::<Runtime>::set_record_xcm(true); + let result = Executive::apply_extrinsic(extrinsic).map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_extrinsic", + "Applying extrinsic failed with error {:?}", + error, + ); + XcmDryRunApiError::InvalidExtrinsic + })?; + let local_xcm = pallet_xcm::Pallet::<Runtime>::recorded_xcm(); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(ExtrinsicDryRunEffects { + local_xcm: local_xcm.map(VersionedXcm::<()>::V4), + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + + fn dry_run_xcm(origin_location: VersionedLocation, program: VersionedXcm<RuntimeCall>) -> Result<XcmDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm::prelude::*; + + let origin_location: Location = origin_location.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Location version conversion failed with error: {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let program: Xcm<RuntimeCall> = program.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Xcm version conversion failed with error {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let mut hash = program.using_encoded(sp_core::hashing::blake2_256); + let result = xcm_executor::XcmExecutor::<xcm_config::XcmConfig>::prepare_and_execute( + origin_location, + program, + &mut hash, + Weight::MAX, // Max limit available for execution. + Weight::zero(), + ); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(XcmDryRunEffects { + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi<Block, Balance, RuntimeCall> for Runtime { @@ -1374,45 +1484,6 @@ impl_runtime_apis! { } } - impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime { - fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> { - let acceptable = vec![ - // native token - VersionedAssetId::from(AssetId(xcm_config::WestendLocation::get())) - ]; - - Ok(acceptable - .into_iter() - .filter_map(|asset| asset.into_version(xcm_version).ok()) - .collect()) - } - - fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { - Ok(asset_id) if asset_id.0 == xcm_config::WestendLocation::get() => { - // for native token - Ok(WeightToFee::weight_to_fee(&weight)) - }, - Ok(asset_id) => { - log::trace!(target: "xcm::xcm_fee_payment_runtime_api", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); - Err(XcmPaymentApiError::AssetNotFound) - }, - Err(_) => { - log::trace!(target: "xcm::xcm_fee_payment_runtime_api", "query_weight_to_asset_fee - failed to convert asset: {asset:?}!"); - Err(XcmPaymentApiError::VersionedConversionFailed) - } - } - } - - fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> { - PolkadotXcm::query_xcm_weight(message) - } - - fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, XcmPaymentApiError> { - PolkadotXcm::query_delivery_fees(destination, message) - } - } - impl cumulus_primitives_core::CollectCollationInfo<Block> for Runtime { fn collect_collation_info(header: &<Block as BlockT>::Header) -> cumulus_primitives_core::CollationInfo { ParachainSystem::collect_collation_info(header) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index d610bfd768cdf43c7efce3ee368b6490fcf054b1..35a42627ad71004642b278bba1ff1a564929061f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -440,6 +440,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index bd1445bee22c3246bf07ba068a8ba0a0490d0770..a0d2e91dffd2e9240ad9364e18ef52a7b3cab3b1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -239,6 +239,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } pub type PriceForParentDelivery = diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index f147cd9653fe7e3eee03bf08a07cf6d3c1d94999..c2ca8e47f2a61cf6d66613daa205caddafe192b0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -206,6 +206,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } pub type PriceForParentDelivery = diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index 84697c3e36341218ad959f575262c0903f5ae96d..c68f230a16dc3d35b861df5aa7667d61d4cf53cf 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -220,6 +220,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Converts a local signed origin into an XCM location. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs index ac15ac5b0f0f524c560f50dcd5ea7a73e2051ae3..8c33710198605f05ec2f3f71c413e03190a3616c 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -202,6 +202,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Converts a local signed origin into an XCM location. diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs index 9095b5b1caaa6f7655ec1f2a0ab6302b20367679..c16b40b8675fbce2878bca4ba1106b89bbd9e9b1 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs @@ -224,6 +224,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Converts a local signed origin into an XCM location. Forms the basis for local origins diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs index defc57e2d7f552a86421359b25d64a2bacbd3572..b12765870bfdbbc7eb495a52e9ec4e931dc442da 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -232,6 +232,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Converts a local signed origin into an XCM location. Forms the basis for local origins diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs index 9d438a41f8fe75037a4c9781653d5f65eee77f9c..d1fb50c1ab095cb07eb3f849c5e5d8be42fd1fc6 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs @@ -91,6 +91,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs index 101d9a180e5feaa36d65afcd01276b86768f4ab0..cca964fb2441bbe8bd40d909eb90766907918df2 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -230,6 +230,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Converts a local signed origin into an XCM location. Forms the basis for local origins diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index 0a903f915056313c5650f030a3c0266eff1612e1..3926ddcf21efe09933e85552b3fc31f368d69e9b 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -238,6 +238,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Converts a local signed origin into an XCM location. Forms the basis for local origins diff --git a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs b/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs index 7f9de0f64b35676f4684b15590a243e4550885d2..741b3bcd752f5511b09add16865a5f311e3626cc 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs @@ -91,6 +91,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 028aa002a91e508c858c7ba71c981d9c9cf8dc60..4ebb95f26cf6ab15b5576a62fe56e2fe626dd6d9 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -64,6 +64,7 @@ polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", def xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +xcm-fee-payment-runtime-api = { path = "../../../../../polkadot/xcm/xcm-fee-payment-runtime-api", default-features = false } # Cumulus cumulus-pallet-aura-ext = { path = "../../../../pallets/aura-ext", default-features = false } @@ -134,6 +135,7 @@ std = [ "substrate-wasm-builder", "xcm-builder/std", "xcm-executor/std", + "xcm-fee-payment-runtime-api/std", "xcm/std", ] @@ -164,6 +166,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 89885d77378ba2c1df13116b5f2d41f547002b30..582154fec6d274e048558fec43893aee7d4f2817 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -32,6 +32,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod weights; pub mod xcm_config; +use codec::Encode; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ @@ -44,7 +45,7 @@ use frame_support::{ AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Everything, TransformOrigin, }, weights::{ - constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, + constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, WeightToFee as _, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, }, PalletId, @@ -80,7 +81,14 @@ pub use sp_runtime::BuildStorage; use parachains_common::{AccountId, Signature}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; -use xcm::latest::prelude::{AssetId as AssetLocationId, BodyId}; +use xcm::{ + latest::prelude::{AssetId as AssetLocationId, BodyId}, + IntoVersion, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm, +}; +use xcm_fee_payment_runtime_api::{ + dry_run::{Error as XcmDryRunApiError, ExtrinsicDryRunEffects, XcmDryRunEffects}, + fees::Error as XcmPaymentApiError, +}; /// Balance of an account. pub type Balance = u128; @@ -835,6 +843,101 @@ impl_runtime_apis! { } } + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi<Block> for Runtime { + fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> { + if !matches!(xcm_version, 3 | 4) { + return Err(XcmPaymentApiError::UnhandledXcmVersion); + } + Ok([VersionedAssetId::V4(xcm_config::RelayLocation::get().into())] + .into_iter() + .filter_map(|asset| asset.into_version(xcm_version).ok()) + .collect()) + } + + fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { + let local_asset = VersionedAssetId::V4(xcm_config::RelayLocation::get().into()); + let asset = asset + .into_version(4) + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; + + if asset != local_asset { return Err(XcmPaymentApiError::AssetNotFound); } + + Ok(WeightToFee::weight_to_fee(&weight)) + } + + fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> { + PolkadotXcm::query_xcm_weight(message) + } + + fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, XcmPaymentApiError> { + PolkadotXcm::query_delivery_fees(destination, message) + } + } + + impl xcm_fee_payment_runtime_api::dry_run::XcmDryRunApi<Block, RuntimeCall, RuntimeEvent> for Runtime { + fn dry_run_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> Result<ExtrinsicDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm_executor::RecordXcm; + use xcm::prelude::*; + + pallet_xcm::Pallet::<Runtime>::set_record_xcm(true); + let result = Executive::apply_extrinsic(extrinsic).map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_extrinsic", + "Applying extrinsic failed with error {:?}", + error, + ); + XcmDryRunApiError::InvalidExtrinsic + })?; + let local_xcm = pallet_xcm::Pallet::<Runtime>::recorded_xcm(); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(ExtrinsicDryRunEffects { + local_xcm: local_xcm.map(VersionedXcm::<()>::V4), + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + + fn dry_run_xcm(origin_location: VersionedLocation, program: VersionedXcm<RuntimeCall>) -> Result<XcmDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm::prelude::*; + + let origin_location: Location = origin_location.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Location version conversion failed with error: {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let program: Xcm<RuntimeCall> = program.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Xcm version conversion failed with error {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let mut hash = program.using_encoded(sp_core::hashing::blake2_256); + let result = xcm_executor::XcmExecutor::<xcm_config::XcmConfig>::prepare_and_execute( + origin_location, + program, + &mut hash, + Weight::MAX, // Max limit. + Weight::zero(), + ); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(XcmDryRunEffects { + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime<Block> for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 711041f6d6e28ccfc53d83ee2b903f06ae65de36..08a2da260c57e67b17eecf55f3845049e371b996 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -362,6 +362,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index e762cec9093b3f5977d60a17fd73660e0e597dc7..f22e900ba9efd66f4abe5561442755e5ce0b5b78 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -490,6 +490,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// Local origins on this chain are allowed to dispatch XCM sends/executions. diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 54f40bd01097c99ab82520c7fe83d13c96c698b2..64784eb36f846f7b325aaaa5ed72966e3d59a1b8 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -34,8 +34,8 @@ use sp_runtime::{ SaturatedConversion, }; use sp_std::{marker::PhantomData, prelude::*}; -use xcm::{latest::prelude::*, WrapVersion}; -use xcm_builder::TakeRevenue; +use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm, WrapVersion}; +use xcm_builder::{InspectMessageQueues, TakeRevenue}; use xcm_executor::{ traits::{MatchesFungibles, TransactAsset, WeightTrader}, AssetsInHolding, @@ -93,6 +93,14 @@ where } } +impl<T: UpwardMessageSender + InspectMessageQueues, W, P> InspectMessageQueues + for ParentAsUmp<T, W, P> +{ + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + T::get_messages() + } +} + /// Contains information to handle refund/payment for xcm-execution #[derive(Clone, Eq, PartialEq, Debug)] struct AssetTraderRefunder { diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 9688ab556473131d6e90075de9b6f69ba4f244e5..7c010778d50d3c1d10884562b6ea2c10fad8a272 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -206,6 +206,7 @@ runtime-benchmarks = [ "service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "westend-runtime?/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/polkadot/node/service/src/fake_runtime_api.rs b/polkadot/node/service/src/fake_runtime_api.rs index 89613040dca1fe306aa9734c149f79dad8bbff57..5c889552a6ae4b7708c0afb7511447fb518f8a9a 100644 --- a/polkadot/node/service/src/fake_runtime_api.rs +++ b/polkadot/node/service/src/fake_runtime_api.rs @@ -398,20 +398,30 @@ sp_api::impl_runtime_apis! { } } - impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime { - fn query_acceptable_payment_assets(_: xcm::Version) -> Result<Vec<VersionedAssetId>, xcm_fee_payment_runtime_api::Error> { + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi<Block> for Runtime { + fn query_acceptable_payment_assets(_: xcm::Version) -> Result<Vec<VersionedAssetId>, xcm_fee_payment_runtime_api::fees::Error> { unimplemented!() } - fn query_weight_to_asset_fee(_: Weight, _: VersionedAssetId) -> Result<u128, xcm_fee_payment_runtime_api::Error> { + fn query_weight_to_asset_fee(_: Weight, _: VersionedAssetId) -> Result<u128, xcm_fee_payment_runtime_api::fees::Error> { unimplemented!() } - fn query_xcm_weight(_: VersionedXcm<()>) -> Result<Weight, xcm_fee_payment_runtime_api::Error> { + fn query_xcm_weight(_: VersionedXcm<()>) -> Result<Weight, xcm_fee_payment_runtime_api::fees::Error> { unimplemented!() } - fn query_delivery_fees(_: VersionedLocation, _: VersionedXcm<()>) -> Result<VersionedAssets, xcm_fee_payment_runtime_api::Error> { + fn query_delivery_fees(_: VersionedLocation, _: VersionedXcm<()>) -> Result<VersionedAssets, xcm_fee_payment_runtime_api::fees::Error> { + unimplemented!() + } + } + + impl xcm_fee_payment_runtime_api::dry_run::XcmDryRunApi<Block, (), ()> for Runtime { + fn dry_run_extrinsic(_: <Block as BlockT>::Extrinsic) -> Result<xcm_fee_payment_runtime_api::dry_run::ExtrinsicDryRunEffects<()>, xcm_fee_payment_runtime_api::dry_run::Error> { + unimplemented!() + } + + fn dry_run_xcm(_: VersionedLocation, _: VersionedXcm<()>) -> Result<xcm_fee_payment_runtime_api::dry_run::XcmDryRunEffects<()>, xcm_fee_payment_runtime_api::dry_run::Error> { unimplemented!() } } diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index a712d4381f75f586ada72b3046925d97a1fba1db..cbec1a8ca1036117275b5234229c2cfc06481056 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -18,7 +18,7 @@ use frame_support::traits::Get; use frame_system::pallet_prelude::BlockNumberFor; -use parity_scale_codec::Encode; +use parity_scale_codec::{Decode, Encode}; use primitives::Id as ParaId; use runtime_parachains::{ configuration::{self, HostConfiguration}, @@ -27,6 +27,7 @@ use runtime_parachains::{ use sp_runtime::FixedPointNumber; use sp_std::{marker::PhantomData, prelude::*}; use xcm::prelude::*; +use xcm_builder::InspectMessageQueues; use SendError::*; /// Simple value-bearing trait for determining/expressing the assets required to be paid for a @@ -138,6 +139,24 @@ where } } +impl<T: dmp::Config, W, P> InspectMessageQueues for ChildParachainRouter<T, W, P> { + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + dmp::DownwardMessageQueues::<T>::iter() + .map(|(para_id, messages)| { + let decoded_messages: Vec<VersionedXcm<()>> = messages + .iter() + .map(|downward_message| { + let message = VersionedXcm::<()>::decode(&mut &downward_message.msg[..]).unwrap(); + log::trace!(target: "xcm::DownwardMessageQueues::get_messages", "Message: {:?}, sent at: {:?}", message, downward_message.sent_at); + message + }) + .collect(); + (VersionedLocation::V4(Parachain(para_id.into()).into()), decoded_messages) + }) + .collect() + } +} + /// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the /// `ParaId` parachain (sibling or child). Deposits existential deposit for origin (if needed). /// Deposits estimated fee to the origin account (if needed). diff --git a/polkadot/runtime/parachains/src/dmp.rs b/polkadot/runtime/parachains/src/dmp.rs index 354b16cc3f082f2f74ad276e8139e21d4094c4cc..df2f93e194214c7bec474668f197ceaca53818ed 100644 --- a/polkadot/runtime/parachains/src/dmp.rs +++ b/polkadot/runtime/parachains/src/dmp.rs @@ -119,7 +119,7 @@ pub mod pallet { /// The downward messages addressed for a certain para. #[pallet::storage] - pub(crate) type DownwardMessageQueues<T: Config> = StorageMap< + pub type DownwardMessageQueues<T: Config> = StorageMap< _, Twox64Concat, ParaId, diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index bbe19310f970ade37e25f5c1e2c983758c617412..f4d8fb51b3fa2bfa95bd409cd8bd7a2aec17c1f8 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -268,6 +268,7 @@ runtime-benchmarks = [ "sp-staking/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ "frame-executive/try-runtime", diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 3b2cbc88dc3fbcb24f8d819237ee9d51231093c0..22e6183e59460899e6eceb938d93222798ee3a34 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -134,7 +134,10 @@ use governance::{ pallet_custom_origins, AuctionAdmin, Fellows, GeneralAdmin, LeaseAdmin, Treasurer, TreasurySpender, }; -use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; +use xcm_fee_payment_runtime_api::{ + dry_run::{Error as XcmDryRunApiError, ExtrinsicDryRunEffects, XcmDryRunEffects}, + fees::Error as XcmPaymentApiError, +}; #[cfg(test)] mod tests; @@ -1764,7 +1767,7 @@ sp_api::impl_runtime_apis! { } } - impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime { + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi<Block> for Runtime { fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> { let acceptable = vec![ // native token @@ -1803,6 +1806,66 @@ sp_api::impl_runtime_apis! { } } + impl xcm_fee_payment_runtime_api::dry_run::XcmDryRunApi<Block, RuntimeCall, RuntimeEvent> for Runtime { + fn dry_run_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> Result<ExtrinsicDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm_executor::RecordXcm; + pallet_xcm::Pallet::<Runtime>::set_record_xcm(true); + let result = Executive::apply_extrinsic(extrinsic).map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_extrinsic", + "Applying extrinsic failed with error {:?}", + error, + ); + XcmDryRunApiError::InvalidExtrinsic + })?; + let local_xcm = pallet_xcm::Pallet::<Runtime>::recorded_xcm(); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(ExtrinsicDryRunEffects { + local_xcm: local_xcm.map(VersionedXcm::<()>::V4), + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + + fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm<RuntimeCall>) -> Result<XcmDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + let origin_location: Location = origin_location.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Location version conversion failed with error: {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Xcm version conversion failed with error {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let result = xcm_executor::XcmExecutor::<xcm_config::XcmConfig>::prepare_and_execute( + origin_location, + xcm, + &mut hash, + Weight::MAX, // Max limit available for execution. + Weight::zero(), + ); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(XcmDryRunEffects { + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + } + impl sp_api::Metadata<Block> for Runtime { fn metadata() -> OpaqueMetadata { OpaqueMetadata::new(Runtime::metadata().into()) diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index c7063bd7ad616c2e5cb7b2e3f8c397087e522c33..decbc795143f006ee50cc98d860e795165269cf5 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -224,6 +224,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; } parameter_types! { diff --git a/polkadot/runtime/test-runtime/src/xcm_config.rs b/polkadot/runtime/test-runtime/src/xcm_config.rs index 8411b79f2529d8e2d26815bd9ab806457e4ca99b..fc3d0dc42a3b93408f15c8363b631aa9761cbe36 100644 --- a/polkadot/runtime/test-runtime/src/xcm_config.rs +++ b/polkadot/runtime/test-runtime/src/xcm_config.rs @@ -156,6 +156,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } impl pallet_xcm::Config for crate::Runtime { diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index d726adfb8e6e439edd2b5f378f5b5160ac70e6be..f02cae0e9d493834b3c0ebe5d33fb3dbc92b9d9a 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -283,6 +283,7 @@ runtime-benchmarks = [ "sp-staking/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ "frame-election-provider-support/try-runtime", diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 8ae95e6e1a83af1e5a7a586f6a206e62d413fbeb..cae12ab49c0242903a99442bea01b02bcecb532c 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -108,7 +108,10 @@ use xcm::{ }; use xcm_builder::PayOverXcm; -use xcm_fee_payment_runtime_api::Error as XcmPaymentApiError; +use xcm_fee_payment_runtime_api::{ + dry_run::{Error as XcmDryRunApiError, ExtrinsicDryRunEffects, XcmDryRunEffects}, + fees::Error as XcmPaymentApiError, +}; pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; @@ -2198,7 +2201,7 @@ sp_api::impl_runtime_apis! { } } - impl xcm_fee_payment_runtime_api::XcmPaymentApi<Block> for Runtime { + impl xcm_fee_payment_runtime_api::fees::XcmPaymentApi<Block> for Runtime { fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> { let acceptable = vec![ // native token @@ -2237,6 +2240,66 @@ sp_api::impl_runtime_apis! { } } + impl xcm_fee_payment_runtime_api::dry_run::XcmDryRunApi<Block, RuntimeCall, RuntimeEvent> for Runtime { + fn dry_run_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> Result<ExtrinsicDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + use xcm_executor::RecordXcm; + pallet_xcm::Pallet::<Runtime>::set_record_xcm(true); + let result = Executive::apply_extrinsic(extrinsic).map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_extrinsic", + "Applying extrinsic failed with error {:?}", + error, + ); + XcmDryRunApiError::InvalidExtrinsic + })?; + let local_xcm = pallet_xcm::Pallet::<Runtime>::recorded_xcm(); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(ExtrinsicDryRunEffects { + local_xcm: local_xcm.map(VersionedXcm::<()>::V4), + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + + fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm<RuntimeCall>) -> Result<XcmDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_builder::InspectMessageQueues; + let origin_location: Location = origin_location.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Location version conversion failed with error: {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Xcm version conversion failed with error {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let result = xcm_executor::XcmExecutor::<xcm_config::XcmConfig>::prepare_and_execute( + origin_location, + xcm, + &mut hash, + Weight::MAX, // Max limit available for execution. + Weight::zero(), + ); + let forwarded_xcms = xcm_config::XcmRouter::get_messages(); + let events: Vec<RuntimeEvent> = System::read_events_no_consensus().map(|record| record.event.clone()).collect(); + Ok(XcmDryRunEffects { + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + } + impl pallet_nomination_pools_runtime_api::NominationPoolsApi< Block, AccountId, diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index f661c4b0e4f437a2f2d597d8bdef26e0b76930c3..c6c5fb9e72a46afa70c6db09fd3a2b0f38d7e3a5 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -222,6 +222,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; } parameter_types! { diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index 7233b46d0cd64d2f649fce9c007da1c1753bcff9..c0dfa91afc7866500b8761753fb83c76b5b23069 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -120,6 +120,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } impl crate::Config for Test { diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs index a9f4d37d7a55f5fa50cd6767447c2578774a7017..f51d34092616b6191cefcb82803673f43f42e4c3 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs @@ -110,6 +110,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } parameter_types! { diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index 460597e6649ab27acec712e1b055fbf5968005f2..fc4d23426fbcce3f5da855c4bc157b6dd986376d 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -69,6 +69,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm-fee-payment-runtime-api/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index af3b66121ea131bba2b8a0e694e59763cd7eb4cc..37fc121ba2174c95f3a8ac82131d525ff8279f48 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -61,7 +61,7 @@ use xcm_executor::{ }, AssetsInHolding, }; -use xcm_fee_payment_runtime_api::Error as FeePaymentError; +use xcm_fee_payment_runtime_api::fees::Error as XcmPaymentApiError; #[cfg(any(feature = "try-runtime", test))] use sp_runtime::TryRuntimeError; @@ -764,6 +764,25 @@ pub mod pallet { #[pallet::storage] pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>; + /// Whether or not incoming XCMs (both executed locally and received) should be recorded. + /// Only one XCM program will be recorded at a time. + /// This is meant to be used in runtime APIs, and it's advised it stays false + /// for all other use cases, so as to not degrade regular performance. + /// + /// Only relevant if this pallet is being used as the [`xcm_executor::traits::RecordXcm`] + /// implementation in the XCM executor configuration. + #[pallet::storage] + pub(crate) type ShouldRecordXcm<T: Config> = StorageValue<_, bool, ValueQuery>; + + /// If [`ShouldRecordXcm`] is set to true, then the last XCM program executed locally + /// will be stored here. + /// Runtime APIs can fetch the XCM that was executed by accessing this value. + /// + /// Only relevant if this pallet is being used as the [`xcm_executor::traits::RecordXcm`] + /// implementation in the XCM executor configuration. + #[pallet::storage] + pub(crate) type RecordedXcm<T: Config> = StorageValue<_, Xcm<()>>; + #[pallet::genesis_config] pub struct GenesisConfig<T: Config> { #[serde(skip)] @@ -2413,35 +2432,37 @@ impl<T: Config> Pallet<T> { AccountIdConversion::<T::AccountId>::into_account_truncating(&ID) } - pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, FeePaymentError> { - let message = - Xcm::<()>::try_from(message).map_err(|_| FeePaymentError::VersionedConversionFailed)?; + pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> { + let message = Xcm::<()>::try_from(message) + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; T::Weigher::weight(&mut message.into()).map_err(|()| { log::error!(target: "xcm::pallet_xcm::query_xcm_weight", "Error when querying XCM weight"); - FeePaymentError::WeightNotComputable + XcmPaymentApiError::WeightNotComputable }) } pub fn query_delivery_fees( destination: VersionedLocation, message: VersionedXcm<()>, - ) -> Result<VersionedAssets, FeePaymentError> { + ) -> Result<VersionedAssets, XcmPaymentApiError> { let result_version = destination.identify_version().max(message.identify_version()); - let destination = - destination.try_into().map_err(|_| FeePaymentError::VersionedConversionFailed)?; + let destination = destination + .try_into() + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; - let message = message.try_into().map_err(|_| FeePaymentError::VersionedConversionFailed)?; + let message = + message.try_into().map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; let (_, fees) = validate_send::<T::XcmRouter>(destination, message).map_err(|error| { log::error!(target: "xcm::pallet_xcm::query_delivery_fees", "Error when querying delivery fees: {:?}", error); - FeePaymentError::Unroutable + XcmPaymentApiError::Unroutable })?; VersionedAssets::from(fees) .into_version(result_version) - .map_err(|_| FeePaymentError::VersionedConversionFailed) + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed) } /// Create a new expectation of a query response with the querier being here. @@ -3105,6 +3126,24 @@ impl<T: Config> CheckSuspension for Pallet<T> { } } +impl<T: Config> xcm_executor::traits::RecordXcm for Pallet<T> { + fn should_record() -> bool { + ShouldRecordXcm::<T>::get() + } + + fn set_record_xcm(enabled: bool) { + ShouldRecordXcm::<T>::put(enabled); + } + + fn recorded_xcm() -> Option<Xcm<()>> { + RecordedXcm::<T>::get() + } + + fn record(xcm: Xcm<()>) { + RecordedXcm::<T>::put(xcm); + } +} + /// Ensure that the origin `o` represents an XCM (`Transact`) origin. /// /// Returns `Ok` with the location of the XCM sender or an `Err` otherwise. diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 8e94803e84315285391c0f85e5d249330a3db31b..b3b7529217f5a4929ce7beedcc1ffa423227112a 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -531,6 +531,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; } pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, AnyNetwork>; diff --git a/polkadot/xcm/pallet-xcm/src/tests/mod.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs index 782c8bed478e18181db64256471fed0dca56e6fd..02aeafd68e83dffd01a7630ce357a4e9cd78cdf1 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/mod.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -21,8 +21,8 @@ pub(crate) mod assets_transfer; use crate::{ mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error, ExecuteControllerWeightInfo, LatestVersionedLocation, Pallet, Queries, QueryStatus, - VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, - WeightInfo, + RecordedXcm, ShouldRecordXcm, VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, + VersionNotifyTargets, WeightInfo, }; use frame_support::{ assert_err_ignore_postinfo, assert_noop, assert_ok, @@ -1245,3 +1245,35 @@ fn multistage_migration_works() { assert!(Pallet::<Test>::do_try_state().is_ok()); }) } + +#[test] +fn record_xcm_works() { + let balances = vec![(ALICE, INITIAL_BALANCE)]; + new_test_ext_with_balances(balances).execute_with(|| { + let message = Xcm::<RuntimeCall>::builder() + .withdraw_asset((Here, SEND_AMOUNT)) + .buy_execution((Here, SEND_AMOUNT), Unlimited) + .deposit_asset(AllCounted(1), Junction::AccountId32 { network: None, id: BOB.into() }) + .build(); + // Test default values. + assert_eq!(ShouldRecordXcm::<Test>::get(), false); + assert_eq!(RecordedXcm::<Test>::get(), None); + + // By default the message won't be recorded. + assert_ok!(XcmPallet::execute( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedXcm::from(message.clone())), + BaseXcmWeight::get() * 3, + )); + assert_eq!(RecordedXcm::<Test>::get(), None); + + // We explicitly set the record flag to true so we record the XCM. + ShouldRecordXcm::<Test>::put(true); + assert_ok!(XcmPallet::execute( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedXcm::from(message.clone())), + BaseXcmWeight::get() * 3, + )); + assert_eq!(RecordedXcm::<Test>::get(), Some(message.into())); + }); +} diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 1ba38d0db836b4e73e3f7785cf0e96f079666441..cc06c298a418d56f6a70d58b5f24bf8ccb14db5d 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -120,7 +120,9 @@ mod process_xcm_message; pub use process_xcm_message::ProcessXcmMessage; mod routing; -pub use routing::{EnsureDecodableXcm, EnsureDelivery, WithTopicSource, WithUniqueTopic}; +pub use routing::{ + EnsureDecodableXcm, EnsureDelivery, InspectMessageQueues, WithTopicSource, WithUniqueTopic, +}; mod transactional; pub use transactional::FrameTransactionalProcessor; diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index 921b9ac5922eaf37e65151b8e3a115e010b0abcb..5c284aaf1475fce0fba7e5cfa042428ceff8268d 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -18,7 +18,7 @@ use frame_system::unique; use parity_scale_codec::Encode; -use sp_std::{marker::PhantomData, result::Result}; +use sp_std::{marker::PhantomData, result::Result, vec::Vec}; use xcm::prelude::*; use xcm_executor::{traits::FeeReason, FeesMode}; @@ -60,6 +60,11 @@ impl<Inner: SendXcm> SendXcm for WithUniqueTopic<Inner> { Ok(unique_id) } } +impl<Inner: InspectMessageQueues> InspectMessageQueues for WithUniqueTopic<Inner> { + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + Inner::get_messages() + } +} pub trait SourceTopic { fn source_topic(entropy: impl Encode) -> XcmHash; @@ -140,6 +145,26 @@ impl EnsureDelivery for Tuple { } } +/// Inspects messages in queues. +/// Meant to be used in runtime APIs, not in runtimes. +pub trait InspectMessageQueues { + /// Get queued messages and their destinations. + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl InspectMessageQueues for Tuple { + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + let mut messages = Vec::new(); + + for_tuples!( #( + messages.append(&mut Tuple::get_messages()); + )* ); + + messages + } +} + /// A wrapper router that attempts to *encode* and *decode* passed XCM `message` to ensure that the /// receiving side will be able to decode, at least with the same XCM version. /// diff --git a/polkadot/xcm/xcm-builder/src/tests/mock.rs b/polkadot/xcm/xcm-builder/src/tests/mock.rs index 7532b97d97b3ba0a8c4ac648d68accc3cadfd6cd..f45650ec5404d14780a3bff3d9bbef0d72a57a25 100644 --- a/polkadot/xcm/xcm-builder/src/tests/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/mock.rs @@ -748,6 +748,7 @@ impl Config for TestConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } pub fn fungible_multi_asset(location: Location, amount: u128) -> Asset { diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 019113a12b2fb127142c9e59d00ef0faca84b688..34b204b434d6e3e547a8b1f5ec48d787bb1fded5 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -221,6 +221,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; } parameter_types! { diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index d0e3ef3032ea1c0aeae698d1e275d4a401a7bb0d..04ceb7e51688994b65701419020ffd5fd5bfdcd7 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -16,6 +16,7 @@ //! Traits and utilities to help with origin mutation and bridging. +use crate::InspectMessageQueues; use frame_support::{ensure, traits::Get}; use parity_scale_codec::{Decode, Encode}; use sp_std::{convert::TryInto, marker::PhantomData, prelude::*}; @@ -335,6 +336,14 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat } } +impl<Bridges, Router: InspectMessageQueues, UniversalLocation> InspectMessageQueues + for SovereignPaidRemoteExporter<Bridges, Router, UniversalLocation> +{ + fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> { + Router::get_messages() + } +} + pub trait DispatchBlob { /// Takes an incoming blob from over some point-to-point link (usually from some sort of /// inter-consensus bridge) and then does what needs to be done with it. Usually this means diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 46ec23beebc1e33c1f3e0d3e25363944f4836cdc..45bfba2355630e4e5b40d1bb9e00114c889811a1 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -209,6 +209,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; } pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, KusamaNetwork>; diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index b296d32ca2adb6761f815aa74397211e3bc17d5f..63b113bc250fa34db10336f7575771bbdbf76b01 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -17,8 +17,8 @@ use crate::traits::{ AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, FeeManager, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, - HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, ShouldExecute, TransactAsset, - VersionChangeNotifier, WeightBounds, WeightTrader, + HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, RecordXcm, ShouldExecute, + TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, }; use frame_support::{ dispatch::{GetDispatchInfo, Parameter, PostDispatchInfo}, @@ -122,4 +122,6 @@ pub trait Config { type HrmpChannelAcceptedHandler: HandleHrmpChannelAccepted; /// Allows optional logic execution for the `HrmpChannelClosing` XCM notification. type HrmpChannelClosingHandler: HandleHrmpChannelClosing; + /// Allows recording the last executed XCM (used by dry-run runtime APIs). + type XcmRecorder: RecordXcm; } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index a7052328da00140faf90d5391f27d126dc953a27..e0b8a8a9c73e4aba1274993d23ac27a77c2a6c78 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -37,6 +37,8 @@ use traits::{ XcmAssetTransfers, }; +pub use traits::RecordXcm; + mod assets; pub use assets::AssetsInHolding; mod config; @@ -211,6 +213,13 @@ impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Con "origin: {origin:?}, message: {message:?}, weight_credit: {weight_credit:?}", ); let mut properties = Properties { weight_credit, message_id: None }; + + // We only want to record under certain conditions (mainly only during dry-running), + // so as to not degrade regular performance. + if Config::XcmRecorder::should_record() { + Config::XcmRecorder::record(message.clone().into()); + } + if let Err(e) = Config::Barrier::should_execute( &origin, message.inner_mut(), diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index aa3f0d26e3025781eacefcf4b6cfdfe140cb39a5..feb2922bcdffce1ea9e3a40e22875c085c82870f 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -49,7 +49,9 @@ mod hrmp; pub use hrmp::{ HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, }; +mod record_xcm; mod weight; +pub use record_xcm::RecordXcm; #[deprecated = "Use `sp_runtime::traits::` instead"] pub use sp_runtime::traits::{Identity, TryConvertInto as JustTry}; pub use weight::{WeightBounds, WeightTrader}; diff --git a/polkadot/xcm/xcm-executor/src/traits/record_xcm.rs b/polkadot/xcm/xcm-executor/src/traits/record_xcm.rs new file mode 100644 index 0000000000000000000000000000000000000000..d0b5bf92d795622038e0272936a00f6eaecef8b2 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/traits/record_xcm.rs @@ -0,0 +1,46 @@ +// Copyright (C) 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 <http://www.gnu.org/licenses/>. + +//! Trait for recording XCMs and a dummy implementation. + +use xcm::latest::Xcm; + +/// Trait for recording XCMs. +pub trait RecordXcm { + /// Whether or not we should record incoming XCMs. + fn should_record() -> bool; + /// Enable or disable recording. + fn set_record_xcm(enabled: bool); + /// Get recorded XCM. + /// Returns `None` if no message was sent, or if recording was off. + fn recorded_xcm() -> Option<Xcm<()>>; + /// Record `xcm`. + fn record(xcm: Xcm<()>); +} + +impl RecordXcm for () { + fn should_record() -> bool { + false + } + + fn set_record_xcm(_: bool) {} + + fn recorded_xcm() -> Option<Xcm<()>> { + None + } + + fn record(_: Xcm<()>) {} +} diff --git a/polkadot/xcm/xcm-fee-payment-runtime-api/Cargo.toml b/polkadot/xcm/xcm-fee-payment-runtime-api/Cargo.toml index 30c7c0bac14f7936d57ec5c86f94bc24ba17013f..cec76e7327ec6145e8513a33264c9419414b0991 100644 --- a/polkadot/xcm/xcm-fee-payment-runtime-api/Cargo.toml +++ b/polkadot/xcm/xcm-fee-payment-runtime-api/Cargo.toml @@ -26,15 +26,46 @@ sp-weights = { path = "../../../substrate/primitives/weights", default-features xcm = { package = "staging-xcm", path = "../", default-features = false } frame-support = { path = "../../../substrate/frame/support", default-features = false } +[dev-dependencies] +frame-system = { path = "../../../substrate/frame/system", default-features = false } +pallet-xcm = { path = "../pallet-xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } +pallet-balances = { path = "../../../substrate/frame/balances", default-features = false } +pallet-assets = { path = "../../../substrate/frame/assets", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../xcm-executor", default-features = false } +frame-executive = { path = "../../../substrate/frame/executive", default-features = false } +log = { workspace = true } +env_logger = "0.9.0" + [features] default = ["std"] std = [ "codec/std", + "frame-executive/std", "frame-support/std", + "frame-system/std", + "log/std", + "pallet-assets/std", + "pallet-balances/std", + "pallet-xcm/std", "scale-info/std", "sp-api/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", "sp-weights/std", + "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] diff --git a/polkadot/xcm/xcm-fee-payment-runtime-api/src/dry_run.rs b/polkadot/xcm/xcm-fee-payment-runtime-api/src/dry_run.rs new file mode 100644 index 0000000000000000000000000000000000000000..62a422d6efeb0928e4f279798382dd139d95ee0a --- /dev/null +++ b/polkadot/xcm/xcm-fee-payment-runtime-api/src/dry_run.rs @@ -0,0 +1,83 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 <http://www.gnu.org/licenses/>. + +//! Runtime API definition for dry-running XCM-related extrinsics. +//! This API can be used to simulate XCMs and, for example, find the fees +//! that need to be paid. + +use codec::{Decode, Encode}; +use frame_support::pallet_prelude::{DispatchResult, TypeInfo}; +use sp_runtime::traits::Block as BlockT; +use sp_std::vec::Vec; +use xcm::prelude::*; + +/// Effects of dry-running an extrinsic. +#[derive(Encode, Decode, Debug, TypeInfo)] +pub struct ExtrinsicDryRunEffects<Event> { + /// The result of executing the extrinsic. + pub execution_result: DispatchResult, + /// The list of events fired by the extrinsic. + pub emitted_events: Vec<Event>, + /// The local XCM that was attempted to be executed, if any. + pub local_xcm: Option<VersionedXcm<()>>, + /// The list of XCMs that were queued for sending. + pub forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, +} + +/// Effects of dry-running an XCM program. +#[derive(Encode, Decode, Debug, TypeInfo)] +pub struct XcmDryRunEffects<Event> { + /// The outcome of the XCM program execution. + pub execution_result: Outcome, + /// List of events fired by the XCM program execution. + pub emitted_events: Vec<Event>, + /// List of queued messages for sending. + pub forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, +} + +sp_api::decl_runtime_apis! { + /// API for dry-running extrinsics and XCM programs to get the programs that need to be passed to the fees API. + /// + /// All calls return a vector of tuples (location, xcm) where each "xcm" is executed in "location". + /// If there's local execution, the location will be "Here". + /// This vector can be used to calculate both execution and delivery fees. + /// + /// Extrinsics or XCMs might fail when executed, this doesn't mean the result of these calls will be an `Err`. + /// In those cases, there might still be a valid result, with the execution error inside it. + /// The only reasons why these calls might return an error are listed in the [`Error`] enum. + pub trait XcmDryRunApi<Call, Event: Decode> { + /// Dry run extrinsic. + fn dry_run_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> Result<ExtrinsicDryRunEffects<Event>, Error>; + + /// Dry run XCM program + fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm<Call>) -> Result<XcmDryRunEffects<Event>, Error>; + } +} + +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum Error { + /// An API call is unsupported. + #[codec(index = 0)] + Unimplemented, + + /// Converting a versioned data structure from one version to another failed. + #[codec(index = 1)] + VersionedConversionFailed, + + /// Extrinsic was invalid. + #[codec(index = 2)] + InvalidExtrinsic, +} diff --git a/polkadot/xcm/xcm-fee-payment-runtime-api/src/fees.rs b/polkadot/xcm/xcm-fee-payment-runtime-api/src/fees.rs new file mode 100644 index 0000000000000000000000000000000000000000..572d4edf533865e66299eba7bd0d22a890547603 --- /dev/null +++ b/polkadot/xcm/xcm-fee-payment-runtime-api/src/fees.rs @@ -0,0 +1,97 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 <http://www.gnu.org/licenses/>. + +//! Runtime API definition for getting XCM fees. + +use codec::{Decode, Encode}; +use frame_support::pallet_prelude::TypeInfo; +use sp_std::vec::Vec; +use sp_weights::Weight; +use xcm::{Version, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}; + +sp_api::decl_runtime_apis! { + /// A trait of XCM payment API. + /// + /// API provides functionality for obtaining: + /// + /// * the weight required to execute an XCM message, + /// * a list of acceptable `AssetId`s for message execution payment, + /// * the cost of the weight in the specified acceptable `AssetId`. + /// * the fees for an XCM message delivery. + /// + /// To determine the execution weight of the calls required for + /// [`xcm::latest::Instruction::Transact`] instruction, `TransactionPaymentCallApi` can be used. + pub trait XcmPaymentApi { + /// Returns a list of acceptable payment assets. + /// + /// # Arguments + /// + /// * `xcm_version`: Version. + fn query_acceptable_payment_assets(xcm_version: Version) -> Result<Vec<VersionedAssetId>, Error>; + + /// Returns a weight needed to execute a XCM. + /// + /// # Arguments + /// + /// * `message`: `VersionedXcm`. + fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, Error>; + + /// Converts a weight into a fee for the specified `AssetId`. + /// + /// # Arguments + /// + /// * `weight`: convertible `Weight`. + /// * `asset`: `VersionedAssetId`. + fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, Error>; + + /// Get delivery fees for sending a specific `message` to a `destination`. + /// These always come in a specific asset, defined by the chain. + /// + /// # Arguments + /// * `message`: The message that'll be sent, necessary because most delivery fees are based on the + /// size of the message. + /// * `destination`: The destination to send the message to. Different destinations may use + /// different senders that charge different fees. + fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, Error>; + } +} + +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum Error { + /// An API part is unsupported. + #[codec(index = 0)] + Unimplemented, + + /// Converting a versioned data structure from one version to another failed. + #[codec(index = 1)] + VersionedConversionFailed, + + /// XCM message weight calculation failed. + #[codec(index = 2)] + WeightNotComputable, + + /// XCM version not able to be handled. + #[codec(index = 3)] + UnhandledXcmVersion, + + /// The given asset is not handled as a fee asset. + #[codec(index = 4)] + AssetNotFound, + + /// Destination is known to be unroutable. + #[codec(index = 5)] + Unroutable, +} diff --git a/polkadot/xcm/xcm-fee-payment-runtime-api/src/lib.rs b/polkadot/xcm/xcm-fee-payment-runtime-api/src/lib.rs index 50fd4692cb0d3ae891395ef445609d0e3a7f0297..616ee4c2eccb0aeed1cdb5e2aa524855d8b2df82 100644 --- a/polkadot/xcm/xcm-fee-payment-runtime-api/src/lib.rs +++ b/polkadot/xcm/xcm-fee-payment-runtime-api/src/lib.rs @@ -14,86 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. -//! Runtime API definition for xcm transaction payment. +//! Runtime APIs for estimating xcm fee payment. +//! This crate offers two APIs, one for estimating fees, +//! which can be used for any type of message, and another one +//! for returning the specific messages used for transfers, a common +//! feature. +//! Users of these APIs should call the transfers API and pass the result to the +//! fees API. #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; -use frame_support::pallet_prelude::TypeInfo; -use sp_std::vec::Vec; -use sp_weights::Weight; -use xcm::{Version, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}; - -sp_api::decl_runtime_apis! { - /// A trait of XCM payment API. - /// - /// API provides functionality for obtaining: - /// - /// * the weight required to execute an XCM message, - /// * a list of acceptable `AssetId`s for message execution payment, - /// * the cost of the weight in the specified acceptable `AssetId`. - /// * the fees for an XCM message delivery. - /// - /// To determine the execution weight of the calls required for - /// [`xcm::latest::Instruction::Transact`] instruction, `TransactionPaymentCallApi` can be used. - pub trait XcmPaymentApi { - /// Returns a list of acceptable payment assets. - /// - /// # Arguments - /// - /// * `xcm_version`: desired XCM `Version` of `VersionedAssetId`. - fn query_acceptable_payment_assets(xcm_version: Version) -> Result<Vec<VersionedAssetId>, Error>; - - /// Returns a weight needed to execute a XCM. - /// - /// # Arguments - /// - /// * `message`: `VersionedXcm`. - fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, Error>; - - /// Converts a weight into a fee for the specified `AssetId`. - /// - /// # Arguments - /// - /// * `weight`: convertible `Weight`. - /// * `asset`: `VersionedAssetId`. - fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, Error>; - - /// Get delivery fees for sending a specific `message` to a `destination`. - /// These always come in a specific asset, defined by the chain. - /// - /// # Arguments - /// * `message`: The message that'll be sent, necessary because most delivery fees are based on the - /// size of the message. - /// * `destination`: The destination to send the message to. Different destinations may use - /// different senders that charge different fees. - fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, Error>; - } -} - -#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] -pub enum Error { - /// An API part is unsupported. - #[codec(index = 0)] - Unimplemented, - - /// Converting a versioned data structure from one version to another failed. - #[codec(index = 1)] - VersionedConversionFailed, - - /// XCM message weight calculation failed. - #[codec(index = 2)] - WeightNotComputable, - - /// XCM version not able to be handled. - #[codec(index = 3)] - UnhandledXcmVersion, - - /// The given asset is not handled as a fee asset. - #[codec(index = 4)] - AssetNotFound, - - /// Destination is known to be unroutable. - #[codec(index = 5)] - Unroutable, -} +/// Dry-run API. +/// Given an extrinsic or an XCM program, it returns the outcome of its execution. +pub mod dry_run; +/// Fee estimation API. +/// Given an XCM program, it will return the fees needed to execute it properly or send it. +pub mod fees; diff --git a/polkadot/xcm/xcm-fee-payment-runtime-api/tests/fee_estimation.rs b/polkadot/xcm/xcm-fee-payment-runtime-api/tests/fee_estimation.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a9bfa4a7968129a13395f2520b6ca1a7b14ff69 --- /dev/null +++ b/polkadot/xcm/xcm-fee-payment-runtime-api/tests/fee_estimation.rs @@ -0,0 +1,370 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 <http://www.gnu.org/licenses/>. + +//! Tests for using both the XCM fee payment API and the dry-run API. + +use frame_support::{ + dispatch::DispatchInfo, + pallet_prelude::{DispatchClass, Pays}, +}; +use sp_api::ProvideRuntimeApi; +use sp_runtime::testing::H256; +use xcm::prelude::*; +use xcm_fee_payment_runtime_api::{dry_run::XcmDryRunApi, fees::XcmPaymentApi}; + +mod mock; +use mock::{ + extra, fake_message_hash, new_test_ext_with_balances, new_test_ext_with_balances_and_assets, + DeliveryFees, ExistentialDeposit, HereLocation, RuntimeCall, RuntimeEvent, TestClient, TestXt, +}; + +// Scenario: User `1` in the local chain (id 2000) wants to transfer assets to account `[0u8; 32]` +// on "AssetHub". He wants to make sure he has enough for fees, so before he calls the +// `transfer_asset` extrinsic to do the transfer, he decides to use the `XcmDryRunApi` and +// `XcmPaymentApi` runtime APIs to estimate fees. This uses a teleport because we're dealing with +// the native token of the chain, which is registered on "AssetHub". The fees are sent as a reserve +// asset transfer, since they're paid in the relay token. +// +// Teleport Parachain(2000) Token +// Reserve Asset Transfer Relay Token for fees +// Parachain(2000) -------------------------------------------> Parachain(1000) +#[test] +fn fee_estimation_for_teleport() { + let _ = env_logger::builder().is_test(true).try_init(); + let who = 1; // AccountId = u64. + let balances = vec![(who, 100 + DeliveryFees::get() + ExistentialDeposit::get())]; + let assets = vec![(1, who, 50)]; + new_test_ext_with_balances_and_assets(balances, assets).execute_with(|| { + let client = TestClient; + let runtime_api = client.runtime_api(); + let extrinsic = TestXt::new( + RuntimeCall::XcmPallet(pallet_xcm::Call::transfer_assets { + dest: Box::new(VersionedLocation::V4((Parent, Parachain(1000)).into())), + beneficiary: Box::new(VersionedLocation::V4( + AccountId32 { id: [0u8; 32], network: None }.into(), + )), + assets: Box::new(VersionedAssets::V4( + vec![(Here, 100u128).into(), (Parent, 20u128).into()].into(), + )), + fee_asset_item: 1, // Fees are paid with the RelayToken + weight_limit: Unlimited, + }), + Some((who, extra())), + ); + let dry_run_effects = + runtime_api.dry_run_extrinsic(H256::zero(), extrinsic).unwrap().unwrap(); + + assert_eq!( + dry_run_effects.local_xcm, + Some(VersionedXcm::V4( + Xcm::builder_unsafe() + .withdraw_asset((Parent, 20u128)) + .burn_asset((Parent, 20u128)) + .withdraw_asset((Here, 100u128)) + .burn_asset((Here, 100u128)) + .build() + )), + ); + let send_destination = Location::new(1, [Parachain(1000)]); + let send_message = Xcm::<()>::builder_unsafe() + .withdraw_asset((Parent, 20u128)) + .buy_execution((Parent, 20u128), Unlimited) + .receive_teleported_asset(((Parent, Parachain(2000)), 100u128)) + .clear_origin() + .deposit_asset(AllCounted(2), [0u8; 32]) + .build(); + assert_eq!( + dry_run_effects.forwarded_xcms, + vec![( + VersionedLocation::V4(send_destination.clone()), + vec![VersionedXcm::V4(send_message.clone())], + ),], + ); + + assert_eq!( + dry_run_effects.emitted_events, + vec![ + RuntimeEvent::System(frame_system::Event::NewAccount { + account: 8660274132218572653 // TODO: Why is this not `1`? + }), + RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: 8660274132218572653, + free_balance: 100 + }), + RuntimeEvent::Balances(pallet_balances::Event::Minted { + who: 8660274132218572653, + amount: 100 + }), + RuntimeEvent::AssetsPallet(pallet_assets::Event::Burned { + asset_id: 1, + owner: 1, + balance: 20 + }), + RuntimeEvent::Balances(pallet_balances::Event::Burned { who: 1, amount: 100 }), + RuntimeEvent::XcmPallet(pallet_xcm::Event::Attempted { + outcome: Outcome::Complete { used: Weight::from_parts(400, 40) }, + }), + RuntimeEvent::Balances(pallet_balances::Event::Burned { who: 1, amount: 20 }), + RuntimeEvent::XcmPallet(pallet_xcm::Event::FeesPaid { + paying: AccountIndex64 { index: 1, network: None }.into(), + fees: (Here, 20u128).into(), + }), + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { + origin: AccountIndex64 { index: 1, network: None }.into(), + destination: (Parent, Parachain(1000)).into(), + message: send_message.clone(), + message_id: fake_message_hash(&send_message), + }), + RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_parts(107074070, 0), /* Will break if weights get + * updated. */ + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + } + }), + ] + ); + + // Weighing the local program is not relevant for extrinsics that already + // take this weight into account. + // In this case, we really only care about delivery fees. + let local_xcm = dry_run_effects.local_xcm.unwrap(); + + // We get a double result since the actual call returns a result and the runtime api returns + // results. + let weight = + runtime_api.query_xcm_weight(H256::zero(), local_xcm.clone()).unwrap().unwrap(); + assert_eq!(weight, Weight::from_parts(400, 40)); + let execution_fees = runtime_api + .query_weight_to_asset_fee( + H256::zero(), + weight, + VersionedAssetId::V4(HereLocation::get().into()), + ) + .unwrap() + .unwrap(); + assert_eq!(execution_fees, 440); + + let mut forwarded_xcms_iter = dry_run_effects.forwarded_xcms.into_iter(); + + let (destination, remote_messages) = forwarded_xcms_iter.next().unwrap(); + let remote_message = &remote_messages[0]; + + let delivery_fees = runtime_api + .query_delivery_fees(H256::zero(), destination.clone(), remote_message.clone()) + .unwrap() + .unwrap(); + assert_eq!(delivery_fees, VersionedAssets::V4((Here, 20u128).into())); + + // This would have to be the runtime API of the destination, + // which we have the location for. + // If I had a mock runtime configured for "AssetHub" then I would use the + // runtime APIs from that. + let remote_execution_weight = runtime_api + .query_xcm_weight(H256::zero(), remote_message.clone()) + .unwrap() + .unwrap(); + let remote_execution_fees = runtime_api + .query_weight_to_asset_fee( + H256::zero(), + remote_execution_weight, + VersionedAssetId::V4(HereLocation::get().into()), + ) + .unwrap() + .unwrap(); + assert_eq!(remote_execution_fees, 550); + + // Now we know that locally we need to use `execution_fees` and + // `delivery_fees`. + // On the message we forward to the destination, we need to + // put `remote_execution_fees` in `BuyExecution`. + // For the `transfer_assets` extrinsic, it just means passing the correct amount + // of fees in the parameters. + }); +} + +// Same scenario as in `fee_estimation_for_teleport`, but the user in parachain 2000 wants +// to send relay tokens over to parachain 1000. +// +// Reserve Asset Transfer Relay Token +// Reserve Asset Transfer Relay Token for fees +// Parachain(2000) -------------------------------------------> Parachain(1000) +#[test] +fn dry_run_reserve_asset_transfer() { + let _ = env_logger::builder().is_test(true).try_init(); + let who = 1; // AccountId = u64. + // Native token used for fees. + let balances = vec![(who, DeliveryFees::get() + ExistentialDeposit::get())]; + // Relay token is the one we want to transfer. + let assets = vec![(1, who, 100)]; // id, account_id, balance. + new_test_ext_with_balances_and_assets(balances, assets).execute_with(|| { + let client = TestClient; + let runtime_api = client.runtime_api(); + let extrinsic = TestXt::new( + RuntimeCall::XcmPallet(pallet_xcm::Call::transfer_assets { + dest: Box::new(VersionedLocation::V4((Parent, Parachain(1000)).into())), + beneficiary: Box::new(VersionedLocation::V4( + AccountId32 { id: [0u8; 32], network: None }.into(), + )), + assets: Box::new(VersionedAssets::V4((Parent, 100u128).into())), + fee_asset_item: 0, + weight_limit: Unlimited, + }), + Some((who, extra())), + ); + let dry_run_effects = + runtime_api.dry_run_extrinsic(H256::zero(), extrinsic).unwrap().unwrap(); + + assert_eq!( + dry_run_effects.local_xcm, + Some(VersionedXcm::V4( + Xcm::builder_unsafe() + .withdraw_asset((Parent, 100u128)) + .burn_asset((Parent, 100u128)) + .build() + )), + ); + + // In this case, the transfer type is `DestinationReserve`, so the remote xcm just withdraws + // the assets. + let send_destination = Location::new(1, Parachain(1000)); + let send_message = Xcm::<()>::builder_unsafe() + .withdraw_asset((Parent, 100u128)) + .clear_origin() + .buy_execution((Parent, 100u128), Unlimited) + .deposit_asset(AllCounted(1), [0u8; 32]) + .build(); + assert_eq!( + dry_run_effects.forwarded_xcms, + vec![( + VersionedLocation::V4(send_destination.clone()), + vec![VersionedXcm::V4(send_message.clone())], + ),], + ); + + assert_eq!( + dry_run_effects.emitted_events, + vec![ + RuntimeEvent::AssetsPallet(pallet_assets::Event::Burned { + asset_id: 1, + owner: 1, + balance: 100 + }), + RuntimeEvent::XcmPallet(pallet_xcm::Event::Attempted { + outcome: Outcome::Complete { used: Weight::from_parts(200, 20) } + }), + RuntimeEvent::Balances(pallet_balances::Event::Burned { who: 1, amount: 20 }), + RuntimeEvent::XcmPallet(pallet_xcm::Event::FeesPaid { + paying: AccountIndex64 { index: 1, network: None }.into(), + fees: (Here, 20u128).into() + }), + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { + origin: AccountIndex64 { index: 1, network: None }.into(), + destination: send_destination.clone(), + message: send_message.clone(), + message_id: fake_message_hash(&send_message), + }), + RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: Weight::from_parts(107074066, 0), /* Will break if weights get + * updated. */ + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + } + }), + ] + ); + }); +} + +#[test] +fn dry_run_xcm() { + let _ = env_logger::builder().is_test(true).try_init(); + let who = 1; // AccountId = u64. + let transfer_amount = 100u128; + // We need to build the XCM to weigh it and then build the real XCM that can pay for fees. + let inner_xcm = Xcm::<()>::builder_unsafe() + .buy_execution((Here, 1u128), Unlimited) // We'd need to query the destination chain for fees. + .deposit_asset(AllCounted(1), [0u8; 32]) + .build(); + let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe() + .withdraw_asset((Here, transfer_amount)) + .clear_origin() + .buy_execution((Here, transfer_amount), Unlimited) + .deposit_reserve_asset(AllCounted(1), (Parent, Parachain(2100)), inner_xcm.clone()) + .build(); + let client = TestClient; + let runtime_api = client.runtime_api(); + let xcm_weight = runtime_api + .query_xcm_weight(H256::zero(), VersionedXcm::V4(xcm_to_weigh.clone().into())) + .unwrap() + .unwrap(); + let execution_fees = runtime_api + .query_weight_to_asset_fee(H256::zero(), xcm_weight, VersionedAssetId::V4(Here.into())) + .unwrap() + .unwrap(); + let xcm = Xcm::<RuntimeCall>::builder_unsafe() + .withdraw_asset((Here, transfer_amount + execution_fees)) + .clear_origin() + .buy_execution((Here, execution_fees), Unlimited) + .deposit_reserve_asset(AllCounted(1), (Parent, Parachain(2100)), inner_xcm.clone()) + .build(); + let balances = vec![( + who, + transfer_amount + execution_fees + DeliveryFees::get() + ExistentialDeposit::get(), + )]; + new_test_ext_with_balances(balances).execute_with(|| { + let dry_run_effects = runtime_api + .dry_run_xcm( + H256::zero(), + VersionedLocation::V4(AccountIndex64 { index: 1, network: None }.into()), + VersionedXcm::V4(xcm), + ) + .unwrap() + .unwrap(); + assert_eq!( + dry_run_effects.forwarded_xcms, + vec![( + VersionedLocation::V4((Parent, Parachain(2100)).into()), + vec![VersionedXcm::V4( + Xcm::<()>::builder_unsafe() + .reserve_asset_deposited(( + (Parent, Parachain(2000)), + transfer_amount + execution_fees - DeliveryFees::get() + )) + .clear_origin() + .buy_execution((Here, 1u128), Unlimited) + .deposit_asset(AllCounted(1), [0u8; 32]) + .build() + )], + ),] + ); + + assert_eq!( + dry_run_effects.emitted_events, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::Burned { who: 1, amount: 540 }), + RuntimeEvent::System(frame_system::Event::NewAccount { account: 2100 }), + RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: 2100, + free_balance: 520 + }), + RuntimeEvent::Balances(pallet_balances::Event::Minted { who: 2100, amount: 520 }), + ] + ); + }); +} diff --git a/polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs b/polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..d7b18d90a501815018fde21e25481872e33753a6 --- /dev/null +++ b/polkadot/xcm/xcm-fee-payment-runtime-api/tests/mock.rs @@ -0,0 +1,525 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 <http://www.gnu.org/licenses/>. + +//! Mock runtime for tests. +//! Implements both runtime APIs for fee estimation and getting the messages for transfers. + +use codec::Encode; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{ + AsEnsureOriginWithArg, ConstU128, ConstU32, Contains, ContainsPair, Everything, Nothing, + OriginTrait, + }, + weights::WeightToFee as WeightToFeeT, +}; +use frame_system::{EnsureRoot, RawOrigin as SystemRawOrigin}; +use pallet_xcm::TestWeightInfo; +use sp_runtime::{ + traits::{Block as BlockT, Get, IdentityLookup, MaybeEquivalence, TryConvert}, + BuildStorage, SaturatedConversion, +}; +use sp_std::{cell::RefCell, marker::PhantomData}; +use xcm::{prelude::*, Version as XcmVersion}; +use xcm_builder::{ + AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, + FixedWeightBounds, FungibleAdapter, FungiblesAdapter, IsConcrete, MintLocation, NoChecking, + TakeWeightCredit, +}; +use xcm_executor::{ + traits::{ConvertLocation, JustTry}, + XcmExecutor, +}; + +use xcm_fee_payment_runtime_api::{ + dry_run::{Error as XcmDryRunApiError, ExtrinsicDryRunEffects, XcmDryRunApi, XcmDryRunEffects}, + fees::{Error as XcmPaymentApiError, XcmPaymentApi}, +}; + +construct_runtime! { + pub enum TestRuntime { + System: frame_system, + Balances: pallet_balances, + AssetsPallet: pallet_assets, + XcmPallet: pallet_xcm, + } +} + +pub type SignedExtra = ( + // frame_system::CheckEra<TestRuntime>, + // frame_system::CheckNonce<TestRuntime>, + frame_system::CheckWeight<TestRuntime>, +); +pub type TestXt = sp_runtime::testing::TestXt<RuntimeCall, SignedExtra>; +type Block = sp_runtime::testing::Block<TestXt>; +type Balance = u128; +type AssetIdForAssetsPallet = u32; +type AccountId = u64; + +pub fn extra() -> SignedExtra { + (frame_system::CheckWeight::new(),) +} + +type Executive = frame_executive::Executive< + TestRuntime, + Block, + frame_system::ChainContext<TestRuntime>, + TestRuntime, + AllPalletsWithSystem, + (), +>; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for TestRuntime { + type Block = Block; + type AccountId = AccountId; + type AccountData = pallet_balances::AccountData<Balance>; + type Lookup = IdentityLookup<AccountId>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; +} + +#[derive_impl(pallet_assets::config_preludes::TestDefaultConfig)] +impl pallet_assets::Config for TestRuntime { + type AssetId = AssetIdForAssetsPallet; + type Balance = Balance; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>; + type ForceOrigin = frame_system::EnsureRoot<AccountId>; + type Freezer = (); + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +thread_local! { + pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = const { RefCell::new(Vec::new()) }; +} + +pub(crate) fn sent_xcm() -> Vec<(Location, Xcm<()>)> { + SENT_XCM.with(|q| (*q.borrow()).clone()) +} + +pub struct TestXcmSender; +impl SendXcm for TestXcmSender { + type Ticket = (Location, Xcm<()>); + fn validate( + dest: &mut Option<Location>, + msg: &mut Option<Xcm<()>>, + ) -> SendResult<Self::Ticket> { + let ticket = (dest.take().unwrap(), msg.take().unwrap()); + let fees: Assets = (HereLocation::get(), DeliveryFees::get()).into(); + Ok((ticket, fees)) + } + fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> { + let hash = fake_message_hash(&ticket.1); + SENT_XCM.with(|q| q.borrow_mut().push(ticket)); + Ok(hash) + } +} + +pub(crate) fn fake_message_hash<Call>(message: &Xcm<Call>) -> XcmHash { + message.using_encoded(sp_io::hashing::blake2_256) +} + +pub type XcmRouter = TestXcmSender; + +parameter_types! { + pub const DeliveryFees: u128 = 20; // Random value. + pub const ExistentialDeposit: u128 = 1; // Random value. + pub const BaseXcmWeight: Weight = Weight::from_parts(100, 10); // Random value. + pub const MaxInstructions: u32 = 100; + pub const NativeTokenPerSecondPerByte: (AssetId, u128, u128) = (AssetId(HereLocation::get()), 1, 1); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(NetworkId::Westend), Parachain(2000)].into(); + pub static AdvertisedXcmVersion: XcmVersion = 4; + pub const HereLocation: Location = Location::here(); + pub const RelayLocation: Location = Location::parent(); + pub const MaxAssetsIntoHolding: u32 = 64; + pub CheckAccount: AccountId = XcmPallet::check_account(); + pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local); + pub const AnyNetwork: Option<NetworkId> = None; +} + +/// Simple `WeightToFee` implementation that adds the ref_time by the proof_size. +pub struct WeightToFee; +impl WeightToFeeT for WeightToFee { + type Balance = Balance; + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + .saturating_add(Self::Balance::saturated_from(weight.proof_size())) + } +} + +type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>; + +/// Matches the pair (NativeToken, AssetHub). +/// This is used in the `IsTeleporter` configuration item, meaning we accept our native token +/// coming from AssetHub as a teleport. +pub struct NativeTokenToAssetHub; +impl ContainsPair<Asset, Location> for NativeTokenToAssetHub { + fn contains(asset: &Asset, origin: &Location) -> bool { + matches!(asset.id.0.unpack(), (0, [])) && matches!(origin.unpack(), (1, [Parachain(1000)])) + } +} + +/// Matches the pair (RelayToken, AssetHub). +/// This is used in the `IsReserve` configuration item, meaning we accept the relay token +/// coming from AssetHub as a reserve asset transfer. +pub struct RelayTokenToAssetHub; +impl ContainsPair<Asset, Location> for RelayTokenToAssetHub { + fn contains(asset: &Asset, origin: &Location) -> bool { + matches!(asset.id.0.unpack(), (1, [])) && matches!(origin.unpack(), (1, [Parachain(1000)])) + } +} + +/// Converts locations that are only the `AccountIndex64` junction into local u64 accounts. +pub struct AccountIndex64Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>); +impl<Network: Get<Option<NetworkId>>, AccountId: From<u64>> ConvertLocation<AccountId> + for AccountIndex64Aliases<Network, AccountId> +{ + fn convert_location(location: &Location) -> Option<AccountId> { + let index = match location.unpack() { + (0, [AccountIndex64 { index, network: None }]) => index, + (0, [AccountIndex64 { index, network }]) if *network == Network::get() => index, + _ => return None, + }; + Some((*index).into()) + } +} + +/// Custom location converter to turn sibling chains into u64 accounts. +pub struct SiblingChainToIndex64; +impl ConvertLocation<AccountId> for SiblingChainToIndex64 { + fn convert_location(location: &Location) -> Option<AccountId> { + let index = match location.unpack() { + (1, [Parachain(id)]) => id, + _ => return None, + }; + Some((*index).into()) + } +} + +/// We alias local account locations to actual local accounts. +/// We also allow sovereign accounts for other sibling chains. +pub type LocationToAccountId = (AccountIndex64Aliases<AnyNetwork, u64>, SiblingChainToIndex64); + +pub type NativeTokenTransactor = FungibleAdapter< + // We use pallet-balances for handling this fungible asset. + Balances, + // The fungible asset handled by this transactor is the native token of the chain. + IsConcrete<HereLocation>, + // How we convert locations to accounts. + LocationToAccountId, + // We need to specify the AccountId type. + AccountId, + // We mint the native tokens locally, so we track how many we've sent away via teleports. + LocalCheckAccount, +>; + +pub struct LocationToAssetIdForAssetsPallet; +impl MaybeEquivalence<Location, AssetIdForAssetsPallet> for LocationToAssetIdForAssetsPallet { + fn convert(location: &Location) -> Option<AssetIdForAssetsPallet> { + match location.unpack() { + (1, []) => Some(1 as AssetIdForAssetsPallet), + _ => None, + } + } + + fn convert_back(id: &AssetIdForAssetsPallet) -> Option<Location> { + match id { + 1 => Some(Location::new(1, [])), + _ => None, + } + } +} + +/// AssetTransactor for handling the relay chain token. +pub type RelayTokenTransactor = FungiblesAdapter< + // We use pallet-assets for handling the relay token. + AssetsPallet, + // Matches the relay token. + ConvertedConcreteId<AssetIdForAssetsPallet, Balance, LocationToAssetIdForAssetsPallet, JustTry>, + // How we convert locations to accounts. + LocationToAccountId, + // We need to specify the AccountId type. + AccountId, + // We don't track teleports. + NoChecking, + (), +>; + +pub type AssetTransactors = (NativeTokenTransactor, RelayTokenTransactor); + +pub struct HereAndInnerLocations; +impl Contains<Location> for HereAndInnerLocations { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, []) | (0, _)) + } +} + +pub type Barrier = ( + TakeWeightCredit, // We need this for pallet-xcm's extrinsics to work. + AllowTopLevelPaidExecutionFrom<HereAndInnerLocations>, /* TODO: Technically, we should allow + * messages from "AssetHub". */ +); + +pub type Trader = FixedRateOfFungible<NativeTokenPerSecondPerByte, ()>; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = (); + type IsReserve = RelayTokenToAssetHub; + type IsTeleporter = NativeTokenToAssetHub; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = Weigher; + type Trader = Trader; + type ResponseHandler = (); + type AssetTrap = (); + type AssetLocker = (); + type AssetExchanger = (); + type AssetClaims = (); + type SubscriptionService = (); + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = (); + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Nothing; + type Aliasers = Nothing; + type TransactionalProcessor = (); + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; +} + +/// Converts a signed origin of a u64 account into a location with only the `AccountIndex64` +/// junction. +pub struct SignedToAccountIndex64<RuntimeOrigin, AccountId>( + PhantomData<(RuntimeOrigin, AccountId)>, +); +impl<RuntimeOrigin: OriginTrait + Clone, AccountId: Into<u64>> TryConvert<RuntimeOrigin, Location> + for SignedToAccountIndex64<RuntimeOrigin, AccountId> +where + RuntimeOrigin::PalletsOrigin: From<SystemRawOrigin<AccountId>> + + TryInto<SystemRawOrigin<AccountId>, Error = RuntimeOrigin::PalletsOrigin>, +{ + fn try_convert(origin: RuntimeOrigin) -> Result<Location, RuntimeOrigin> { + origin.try_with_caller(|caller| match caller.try_into() { + Ok(SystemRawOrigin::Signed(who)) => + Ok(Junction::AccountIndex64 { network: None, index: who.into() }.into()), + Ok(other) => Err(other.into()), + Err(other) => Err(other), + }) + } +} + +pub type LocalOriginToLocation = SignedToAccountIndex64<RuntimeOrigin, AccountId>; + +impl pallet_xcm::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>; + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor<XcmConfig>; + type XcmTeleportFilter = Everything; // Put everything instead of something more restricted. + type XcmReserveTransferFilter = Everything; // Same. + type Weigher = Weigher; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = AdvertisedXcmVersion; + type AdminOrigin = EnsureRoot<AccountId>; + type TrustedLockers = (); + type SovereignAccountOf = (); + type Currency = Balances; + type CurrencyMatcher = IsConcrete<HereLocation>; + type MaxLockers = ConstU32<0>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type WeightInfo = TestWeightInfo; +} + +pub fn new_test_ext_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig::<TestRuntime> { balances } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub fn new_test_ext_with_balances_and_assets( + balances: Vec<(AccountId, Balance)>, + assets: Vec<(AssetIdForAssetsPallet, AccountId, Balance)>, +) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig::<TestRuntime> { balances } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_assets::GenesisConfig::<TestRuntime> { + assets: vec![ + // id, owner, is_sufficient, min_balance. + // We don't actually need this to be sufficient, since we use the native assets in + // tests for the existential deposit. + (1, 0, true, 1), + ], + metadata: vec![ + // id, name, symbol, decimals. + (1, "Relay Token".into(), "RLY".into(), 12), + ], + accounts: assets, + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +#[derive(Clone)] +pub(crate) struct TestClient; + +pub(crate) struct RuntimeApi { + _inner: TestClient, +} + +impl sp_api::ProvideRuntimeApi<Block> for TestClient { + type Api = RuntimeApi; + fn runtime_api(&self) -> sp_api::ApiRef<Self::Api> { + RuntimeApi { _inner: self.clone() }.into() + } +} + +sp_api::mock_impl_runtime_apis! { + impl XcmPaymentApi<Block> for RuntimeApi { + fn query_acceptable_payment_assets(xcm_version: XcmVersion) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> { + if xcm_version != 4 { return Err(XcmPaymentApiError::UnhandledXcmVersion) }; + Ok(vec![VersionedAssetId::V4(HereLocation::get().into())]) + } + + fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> { + XcmPallet::query_xcm_weight(message) + } + + fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { + let local_asset = VersionedAssetId::V4(HereLocation::get().into()); + let asset = asset + .into_version(4) + .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; + + if asset != local_asset { return Err(XcmPaymentApiError::AssetNotFound); } + + Ok(WeightToFee::weight_to_fee(&weight)) + } + + fn query_delivery_fees(destination: VersionedLocation, message: VersionedXcm<()>) -> Result<VersionedAssets, XcmPaymentApiError> { + XcmPallet::query_delivery_fees(destination, message) + } + } + + impl XcmDryRunApi<Block, RuntimeCall, RuntimeEvent> for RuntimeApi { + fn dry_run_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> Result<ExtrinsicDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + use xcm_executor::RecordXcm; + // We want to record the XCM that's executed, so we can return it. + pallet_xcm::Pallet::<TestRuntime>::set_record_xcm(true); + let result = Executive::apply_extrinsic(extrinsic).map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_extrinsic", + "Applying extrinsic failed with error {:?}", + error, + ); + XcmDryRunApiError::InvalidExtrinsic + })?; + // Nothing gets committed to storage in runtime APIs, so there's no harm in leaving the flag as true. + let local_xcm = pallet_xcm::Pallet::<TestRuntime>::recorded_xcm(); + let forwarded_xcms = sent_xcm() + .into_iter() + .map(|(location, message)| ( + VersionedLocation::V4(location), + vec![VersionedXcm::V4(message)], + )).collect(); + let events: Vec<RuntimeEvent> = System::events().iter().map(|record| record.event.clone()).collect(); + Ok(ExtrinsicDryRunEffects { + local_xcm: local_xcm.map(VersionedXcm::<()>::V4), + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + + fn dry_run_xcm(origin_location: VersionedLocation, xcm: VersionedXcm<RuntimeCall>) -> Result<XcmDryRunEffects<RuntimeEvent>, XcmDryRunApiError> { + let origin_location: Location = origin_location.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Location version conversion failed with error: {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| { + log::error!( + target: "xcm::XcmDryRunApi::dry_run_xcm", + "Xcm version conversion failed with error {:?}", + error, + ); + XcmDryRunApiError::VersionedConversionFailed + })?; + let mut hash = fake_message_hash(&xcm); + let result = XcmExecutor::<XcmConfig>::prepare_and_execute( + origin_location, + xcm, + &mut hash, + Weight::MAX, // Max limit available for execution. + Weight::zero(), + ); + let forwarded_xcms = sent_xcm() + .into_iter() + .map(|(location, message)| ( + VersionedLocation::V4(location), + vec![VersionedXcm::V4(message)], + )).collect(); + let events: Vec<RuntimeEvent> = System::events().iter().map(|record| record.event.clone()).collect(); + Ok(XcmDryRunEffects { + forwarded_xcms, + emitted_events: events, + execution_result: result, + }) + } + } +} diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/mod.rs b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/mod.rs index 0ba02aab9bf92ac699485fcd5803e2c658574d13..a6b55d1bd9be05641db343f2f301ae560e0fea29 100644 --- a/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/mod.rs +++ b/polkadot/xcm/xcm-simulator/example/src/parachain/xcm_config/mod.rs @@ -60,4 +60,5 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs index a7a8bae51567b3a526333fc56e14ff38c337c776..c5d5fa66732b939b9a03e1da2cf9c658c4e61acb 100644 --- a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs @@ -59,4 +59,5 @@ impl Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; } diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs index d8d65fbf0ce74a17b75e6bff8c9acfc02dbf928c..502bcca2d44270263a45eeaf305b924d8b37c509 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -159,6 +159,7 @@ impl Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } #[frame_support::pallet] diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index cf3ca0de2bb46a8abe6f5fcd28dbf19ce5faf41b..4740aee83d870a0e260512ac15a982c515a1e803 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -160,6 +160,7 @@ impl Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = (); } pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, ThisNetwork>; diff --git a/prdoc/pr_3872.prdoc b/prdoc/pr_3872.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3a5be3d2bc74d08ec83aa1cacaceb294e948c563 --- /dev/null +++ b/prdoc/pr_3872.prdoc @@ -0,0 +1,86 @@ +title: XcmDryRunApi - Runtime API for dry-running extrinsics and XCM programs. + +doc: + - audience: Runtime Dev + description: | + This PR introduces a new runtime API, the XcmDryRunApi, that allows dry-running + extrinsics and XCM programs to get their execution effects. + These effects include: + - Local execution result, either pass or fail + - Emitted events + - Forwarded XCMs + - In the case of extrinsics, the XCM program that they execute + This API can be used on its own to test extrinsics or XCM programs, + or used alongside the XcmPaymentApi to estimate execution and delivery + fees. + + This PR also adds a new configuration item to XCM: XcmRecorder. + This can be set to either (), the xcm pallet, or some custom implementation. + If set to (), the dry run API will not return the local XCM program executed + by running an extrinsic. + After this PR, it is necessary to add the new configuration item to your xcm + configs. + - audience: Runtime User + description: | + This PR introduces a new runtime API, the XcmDryRunApi, that allows dry-running + extrinsics and XCM programs to get their execution effects. + These effects include: + - Local execution result, either pass or fail + - Emitted events + - Forwarded XCMs + - In the case of extrinsics, the XCM program that they execute + This API can be used on its own to test extrinsics or XCM programs, + or used alongside the XcmPaymentApi to estimate execution and delivery + fees. + +crates: + - name: xcm-fee-payment-runtime-api + bump: major + - name: pallet-xcm + bump: minor + - name: staging-xcm-executor + bump: minor + - name: staging-xcm-builder + bump: minor + - name: rococo-runtime + bump: minor + - name: westend-runtime + bump: minor + - name: pallet-xcm-benchmarks + bump: minor + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor + - name: bridge-hub-rococo-runtime + bump: minor + - name: bridge-hub-westend-runtime + bump: minor + - name: collectives-westend-runtime + bump: minor + - name: contracts-rococo-runtime + bump: minor + - name: coretime-rococo-runtime + bump: minor + - name: coretime-westend-runtime + bump: minor + - name: glutton-westend-runtime + bump: minor + - name: people-rococo-runtime + bump: minor + - name: people-westend-runtime + bump: minor + - name: shell-runtime + bump: minor + - name: penpal-runtime + bump: minor + - name: rococo-parachain-runtime + bump: minor + - name: polkadot-service + bump: minor + - name: polkadot-test-runtime + bump: minor + - name: parachain-template-runtime + bump: minor + - name: pallet-contracts-mock-network + bump: minor diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index 843efab1502e7304991682d1f13180ee28288fe2..b46d7df6c2bcf1f99a48540d5f1c5ef104d09206 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -285,6 +285,7 @@ impl Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } impl mock_msg_queue::Config for Runtime { diff --git a/substrate/frame/contracts/mock-network/src/relay_chain.rs b/substrate/frame/contracts/mock-network/src/relay_chain.rs index d5e0ec9c83fa73cc88f567f573f0d2172af23a7a..36a7de499ba99311889980ba2c02d8502602b945 100644 --- a/substrate/frame/contracts/mock-network/src/relay_chain.rs +++ b/substrate/frame/contracts/mock-network/src/relay_chain.rs @@ -185,6 +185,7 @@ impl Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; } pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>; diff --git a/templates/parachain/runtime/src/configs/xcm_config.rs b/templates/parachain/runtime/src/configs/xcm_config.rs index c6b6e8da1b89d38a8f5f7695809972d35d83cf12..e162bcbf88686c00ea10dc9b06c985329faa763b 100644 --- a/templates/parachain/runtime/src/configs/xcm_config.rs +++ b/templates/parachain/runtime/src/configs/xcm_config.rs @@ -142,6 +142,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; } /// No local origins on this chain are allowed to dispatch XCM sends/executions.