Skip to content

Commit 5bb025e

Browse files
franciscoaguirreacatangiu
authored andcommitted
XcmDryRunApi - Dry-running extrinsics to get their XCM effects (paritytech#3872)
# Context Estimating fees for XCM execution and sending has been an area with bad UX. The addition of the [XcmPaymentApi](paritytech#3607) exposed the necessary components to be able to estimate XCM fees correctly, however, that was not the full story. The `XcmPaymentApi` works for estimating fees only if you know the specific XCM you want to execute or send. This is necessary but most UIs want to estimate the fees for extrinsics, they don't necessarily know the XCM program that's executed by them. # Main addition A new runtime API is introduced, the `XcmDryRunApi`, that given an extrinsic, or an XCM program, returns its effects: - Execution result - Local XCM (in the case of an extrinsic) - Forwarded XCMs - List of events This API can be used on its own for dry-running purposes, for double-checking or testing, but it mainly shines when used in conjunction with the `XcmPaymentApi`. UIs can use these two APIs to estimate transfers. # How it works New tests are added to exemplify how to incorporate both APIs. There's a mock test just to make sure everything works under `xcm-fee-payment-runtime-api`. There's a real-world test using Westend and AssetHubWestend under `cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs`. Added both a test for a simple teleport between chains and a reserve transfer asset between two parachains going through a reserve. The steps to follow: - Use `XcmDryRunApi::dry_run_extrinsic` to get local XCM program and forwarded messages - For each forwarded message - Use `XcmPaymentApi::query_delivery_fee` LOCALLY to get the delivery fees - Use `XcmPaymentApi::query_xcm_weight` ON THE DESTINATION to get the remote execution weight - (optional) Use `XcmPaymentApi::query_acceptable_payment_assets` ON THE DESTINATION to know on which assets the execution fees can be paid - Use `XcmPaymentApi::query_weight_to_asset_fee` ON THE DESTINATION to convert weight to the actual remote execution fees - Use `XcmDryRunApi::dry_run_xcm` ON THE DESTINATION to know if a new message will be forwarded, if so, continue # Dear reviewer The changes in this PR are grouped as follows, and in order of importance: - Addition of new runtime API - Definition, mock and simple tests: polkadot/xcm/xcm-fee-payment-runtime-api/* - Implemented on Westend, Asset Hub Westend and Penpal, will implement on every runtime in a following PR - Addition of a new config item to the XCM executor for recording xcms about to be executed - Definition: polkadot/xcm/xcm-executor/* - Implementation: polkadot/xcm/pallet-xcm/* - had to update all runtime xcm_config.rs files with `type XcmRecorder = XcmPallet;` - Addition of a new trait for inspecting the messages in queues - Definition: polkadot/xcm/xcm-builder/src/routing.rs - Implemented it on all routers: - ChildParachainRouter: polkadot/runtime/common/src/xcm_sender.rs - ParentAsUmp: cumulus/primitives/utility/src/lib.rs (piggybacked on implementation in cumulus/pallets/parachain-system/src/lib.rs) - XcmpQueue: cumulus/pallets/xcmp-queue/src/lib.rs - Bridge: bridges/modules/xcm-bridge-hub-router/src/lib.rs - More complicated and useful tests: - cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs ## Next steps With this PR, Westend, AssetHubWestend, Rococo and AssetHubRococo have the new API. UIs can test on these runtimes to create better experiences around cross-chain operations. Next: - Add XcmDryRunApi to all system parachains - Integrate xcm fee estimation in all emulated tests - Get this on the fellowship runtimes --------- Co-authored-by: Adrian Catangiu <adrian@parity.io>
1 parent f3beafe commit 5bb025e

75 files changed

Lines changed: 2107 additions & 175 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bridges/modules/xcm-bridge-hub-router/src/lib.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ use codec::Encode;
3737
use frame_support::traits::Get;
3838
use sp_core::H256;
3939
use sp_runtime::{FixedPointNumber, FixedU128, Saturating};
40+
use sp_std::vec::Vec;
4041
use xcm::prelude::*;
41-
use xcm_builder::{ExporterFor, SovereignPaidRemoteExporter};
42+
use xcm_builder::{ExporterFor, InspectMessageQueues, SovereignPaidRemoteExporter};
4243

4344
pub use pallet::*;
4445
pub use weights::WeightInfo;
@@ -95,7 +96,7 @@ pub mod pallet {
9596
/// Origin of the sibling bridge hub that is allowed to report bridge status.
9697
type BridgeHubOrigin: EnsureOrigin<Self::RuntimeOrigin>;
9798
/// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location.
98-
type ToBridgeHubSender: SendXcm;
99+
type ToBridgeHubSender: SendXcm + InspectMessageQueues;
99100
/// Underlying channel with the sibling bridge hub. It must match the channel, used
100101
/// by the `Self::ToBridgeHubSender`.
101102
type WithBridgeHubChannel: XcmChannelStatusProvider;
@@ -396,6 +397,12 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
396397
}
397398
}
398399

400+
impl<T: Config<I>, I: 'static> InspectMessageQueues for Pallet<T, I> {
401+
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
402+
ViaBridgeHubExporter::<T, I>::get_messages()
403+
}
404+
}
405+
399406
#[cfg(test)]
400407
mod tests {
401408
use super::*;
@@ -635,4 +642,36 @@ mod tests {
635642
);
636643
});
637644
}
645+
646+
#[test]
647+
fn get_messages_works() {
648+
run_test(|| {
649+
assert_ok!(send_xcm::<XcmBridgeHubRouter>(
650+
(Parent, Parent, GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)).into(),
651+
vec![ClearOrigin].into()
652+
));
653+
assert_eq!(
654+
XcmBridgeHubRouter::get_messages(),
655+
vec![(
656+
VersionedLocation::V4((Parent, Parachain(1002)).into()),
657+
vec![VersionedXcm::V4(
658+
Xcm::builder()
659+
.withdraw_asset((Parent, 1_002_000))
660+
.buy_execution((Parent, 1_002_000), Unlimited)
661+
.set_appendix(
662+
Xcm::builder_unsafe()
663+
.deposit_asset(AllCounted(1), (Parent, Parachain(1000)))
664+
.build()
665+
)
666+
.export_message(
667+
Kusama,
668+
Parachain(1000),
669+
Xcm::builder_unsafe().clear_origin().build()
670+
)
671+
.build()
672+
)],
673+
),],
674+
);
675+
});
676+
}
638677
}

bridges/modules/xcm-bridge-hub-router/src/mock.rs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@
1919
use crate as pallet_xcm_bridge_hub_router;
2020

2121
use bp_xcm_bridge_hub_router::XcmChannelStatusProvider;
22+
use codec::Encode;
2223
use frame_support::{
2324
construct_runtime, derive_impl, parameter_types,
2425
traits::{Contains, Equals},
2526
};
2627
use frame_system::EnsureRoot;
2728
use sp_runtime::{traits::ConstU128, BuildStorage};
29+
use sp_std::cell::RefCell;
2830
use xcm::prelude::*;
29-
use xcm_builder::{NetworkExportTable, NetworkExportTableItem};
31+
use xcm_builder::{InspectMessageQueues, NetworkExportTable, NetworkExportTableItem};
3032

3133
pub type AccountId = u64;
3234
type Block = frame_system::mocking::MockBlock<TestRuntime>;
@@ -102,23 +104,46 @@ pub struct TestToBridgeHubSender;
102104

103105
impl TestToBridgeHubSender {
104106
pub fn is_message_sent() -> bool {
105-
frame_support::storage::unhashed::get_or_default(b"TestToBridgeHubSender.Sent")
107+
!Self::get_messages().is_empty()
106108
}
107109
}
108110

111+
thread_local! {
112+
pub static SENT_XCM: RefCell<Vec<(Location, Xcm<()>)>> = RefCell::new(Vec::new());
113+
}
114+
109115
impl SendXcm for TestToBridgeHubSender {
110-
type Ticket = ();
116+
type Ticket = (Location, Xcm<()>);
111117

112118
fn validate(
113-
_destination: &mut Option<Location>,
114-
_message: &mut Option<Xcm<()>>,
119+
destination: &mut Option<Location>,
120+
message: &mut Option<Xcm<()>>,
115121
) -> SendResult<Self::Ticket> {
116-
Ok(((), (BridgeFeeAsset::get(), HRMP_FEE).into()))
122+
let pair = (destination.take().unwrap(), message.take().unwrap());
123+
Ok((pair, (BridgeFeeAsset::get(), HRMP_FEE).into()))
117124
}
118125

119-
fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
120-
frame_support::storage::unhashed::put(b"TestToBridgeHubSender.Sent", &true);
121-
Ok([0u8; 32])
126+
fn deliver(pair: Self::Ticket) -> Result<XcmHash, SendError> {
127+
let hash = fake_message_hash(&pair.1);
128+
SENT_XCM.with(|q| q.borrow_mut().push(pair));
129+
Ok(hash)
130+
}
131+
}
132+
133+
impl InspectMessageQueues for TestToBridgeHubSender {
134+
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
135+
SENT_XCM.with(|q| {
136+
(*q.borrow())
137+
.clone()
138+
.iter()
139+
.map(|(location, message)| {
140+
(
141+
VersionedLocation::V4(location.clone()),
142+
vec![VersionedXcm::V4(message.clone())],
143+
)
144+
})
145+
.collect()
146+
})
122147
}
123148
}
124149

@@ -146,3 +171,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
146171
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
147172
new_test_ext().execute_with(test)
148173
}
174+
175+
pub(crate) fn fake_message_hash<T>(message: &Xcm<T>) -> XcmHash {
176+
message.using_encoded(sp_io::hashing::blake2_256)
177+
}

cumulus/pallets/parachain-system/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-
3838
polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false }
3939
polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false, optional = true }
4040
xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false }
41+
xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false }
4142

4243
# Cumulus
4344
cumulus-pallet-parachain-system-proc-macro = { path = "proc-macro", default-features = false }
@@ -95,6 +96,7 @@ std = [
9596
"sp-tracing/std",
9697
"sp-trie/std",
9798
"trie-db/std",
99+
"xcm-builder/std",
98100
"xcm/std",
99101
]
100102

@@ -109,6 +111,7 @@ runtime-benchmarks = [
109111
"polkadot-runtime-common/runtime-benchmarks",
110112
"polkadot-runtime-parachains/runtime-benchmarks",
111113
"sp-runtime/runtime-benchmarks",
114+
"xcm-builder/runtime-benchmarks",
112115
]
113116

114117
try-runtime = [

cumulus/pallets/parachain-system/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ use sp_runtime::{
5555
BoundedSlice, FixedU128, RuntimeDebug, Saturating,
5656
};
5757
use sp_std::{cmp, collections::btree_map::BTreeMap, prelude::*};
58-
use xcm::latest::XcmHash;
58+
use xcm::{latest::XcmHash, VersionedLocation, VersionedXcm};
59+
use xcm_builder::InspectMessageQueues;
5960

6061
mod benchmarking;
6162
pub mod migration;
@@ -1612,6 +1613,19 @@ impl<T: Config> UpwardMessageSender for Pallet<T> {
16121613
}
16131614
}
16141615

1616+
impl<T: Config> InspectMessageQueues for Pallet<T> {
1617+
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
1618+
use xcm::prelude::*;
1619+
1620+
let messages: Vec<VersionedXcm<()>> = PendingUpwardMessages::<T>::get()
1621+
.iter()
1622+
.map(|encoded_message| VersionedXcm::<()>::decode(&mut &encoded_message[..]).unwrap())
1623+
.collect();
1624+
1625+
vec![(VersionedLocation::V4(Parent.into()), messages)]
1626+
}
1627+
}
1628+
16151629
#[cfg(feature = "runtime-benchmarks")]
16161630
impl<T: Config> polkadot_runtime_common::xcm_sender::EnsureForParachain for Pallet<T> {
16171631
fn ensure(para_id: ParaId) {

cumulus/pallets/xcmp-queue/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-f
2828
polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false }
2929
xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false }
3030
xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false }
31+
xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false }
3132

3233
# Cumulus
3334
cumulus-primitives-core = { path = "../../primitives/core", default-features = false }
@@ -46,9 +47,6 @@ sp-core = { path = "../../../substrate/primitives/core" }
4647
pallet-balances = { path = "../../../substrate/frame/balances" }
4748
frame-support = { path = "../../../substrate/frame/support", features = ["experimental"] }
4849

49-
# Polkadot
50-
xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder" }
51-
5250
# Cumulus
5351
cumulus-pallet-parachain-system = { path = "../parachain-system", features = ["parameterized-consensus-hook"] }
5452

@@ -71,6 +69,7 @@ std = [
7169
"sp-io/std",
7270
"sp-runtime/std",
7371
"sp-std/std",
72+
"xcm-builder/std",
7473
"xcm-executor/std",
7574
"xcm/std",
7675
]

cumulus/pallets/xcmp-queue/src/lib.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ use scale_info::TypeInfo;
7070
use sp_core::MAX_POSSIBLE_ALLOCATION;
7171
use sp_runtime::{FixedU128, RuntimeDebug, Saturating};
7272
use sp_std::prelude::*;
73-
use xcm::{latest::prelude::*, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH};
73+
use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH};
74+
use xcm_builder::InspectMessageQueues;
7475
use xcm_executor::traits::ConvertOrigin;
7576

7677
pub use pallet::*;
@@ -947,6 +948,38 @@ impl<T: Config> SendXcm for Pallet<T> {
947948
}
948949
}
949950

951+
impl<T: Config> InspectMessageQueues for Pallet<T> {
952+
fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
953+
use xcm::prelude::*;
954+
955+
OutboundXcmpMessages::<T>::iter()
956+
.map(|(para_id, _, messages)| {
957+
let mut data = &messages[..];
958+
let decoded_format =
959+
XcmpMessageFormat::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut data)
960+
.unwrap();
961+
if decoded_format != XcmpMessageFormat::ConcatenatedVersionedXcm {
962+
panic!("Unexpected format.")
963+
}
964+
let mut decoded_messages = Vec::new();
965+
while !data.is_empty() {
966+
let decoded_message = VersionedXcm::<()>::decode_with_depth_limit(
967+
MAX_XCM_DECODE_DEPTH,
968+
&mut data,
969+
)
970+
.unwrap();
971+
decoded_messages.push(decoded_message);
972+
}
973+
974+
(
975+
VersionedLocation::V4((Parent, Parachain(para_id.into())).into()),
976+
decoded_messages,
977+
)
978+
})
979+
.collect()
980+
}
981+
}
982+
950983
impl<T: Config> FeeTracker for Pallet<T> {
951984
type Id = ParaId;
952985

cumulus/pallets/xcmp-queue/src/mock.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ impl xcm_executor::Config for XcmConfig {
178178
type HrmpNewChannelOpenRequestHandler = ();
179179
type HrmpChannelAcceptedHandler = ();
180180
type HrmpChannelClosingHandler = ();
181+
type XcmRecorder = ();
181182
}
182183

183184
pub type XcmRouter = (

0 commit comments

Comments
 (0)