referrerpolicy=no-referrer-when-downgrade

pallet_xcm/
benchmarking.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use super::*;
18use frame_benchmarking::v2::*;
19use frame_support::{assert_ok, weights::Weight};
20use frame_system::RawOrigin;
21use xcm::{
22	latest::{prelude::*, MAX_ITEMS_IN_ASSETS},
23	MAX_INSTRUCTIONS_TO_DECODE,
24};
25use xcm_builder::EnsureDelivery;
26use xcm_executor::traits::{FeeReason, WeightBounds};
27
28type RuntimeOrigin<T> = <T as frame_system::Config>::RuntimeOrigin;
29
30/// Upper bound for a caller-supplied XCM blob, in bytes.
31///
32/// Meant to be kept in sync with `pallet_revive::limits::CALLDATA_BYTES`, the largest input a
33/// contract can hand to the XCM precompile.
34const MAX_XCM_BLOB_BYTES: u32 = 128 * 1024;
35
36/// Upper bound for the `weigh_message` benchmark's input, in bytes.
37const MAX_WEIGHABLE_BLOB_BYTES: u32 = 8 * 1024;
38
39/// Pallet we're benchmarking here.
40pub struct Pallet<T: Config>(crate::Pallet<T>);
41
42/// Trait that must be implemented by runtime to be able to benchmark pallet properly.
43pub trait Config: crate::Config + pallet_balances::Config {
44	/// Helper that ensures successful delivery for extrinsics/benchmarks which need `SendXcm`.
45	type DeliveryHelper: EnsureDelivery;
46
47	/// A `Location` that can be reached via `XcmRouter`. Used only in benchmarks.
48	///
49	/// If `None`, the benchmarks that depend on a reachable destination will be skipped.
50	fn reachable_dest() -> Option<Location> {
51		None
52	}
53
54	/// A `(Asset, Location)` pair representing asset and the destination it can be
55	/// teleported to. Used only in benchmarks.
56	///
57	/// Implementation should also make sure `dest` is reachable/connected.
58	///
59	/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
60	fn teleportable_asset_and_dest() -> Option<(Asset, Location)> {
61		None
62	}
63
64	/// A `(Asset, Location)` pair representing asset and the destination it can be
65	/// reserve-transferred to. Used only in benchmarks.
66	///
67	/// Implementation should also make sure `dest` is reachable/connected.
68	///
69	/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
70	fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> {
71		None
72	}
73
74	/// Sets up a complex transfer (usually consisting of a teleport and reserve-based transfer), so
75	/// that runtime can properly benchmark `transfer_assets()` extrinsic. Should return a tuple
76	/// `(Asset, u32, Location, dyn FnOnce())` representing the assets to transfer, the
77	/// `u32` index of the asset to be used for fees, the destination chain for the transfer, and a
78	/// `verify()` closure to verify the intended transfer side-effects.
79	///
80	/// Implementation should make sure the provided assets can be transacted by the runtime, there
81	/// are enough balances in the involved accounts, and that `dest` is reachable/connected.
82	///
83	/// Used only in benchmarks.
84	///
85	/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
86	fn set_up_complex_asset_transfer() -> Option<(Assets, u32, Location, Box<dyn FnOnce()>)> {
87		None
88	}
89
90	/// Gets an asset that can be handled by the AssetTransactor.
91	///
92	/// Used only in benchmarks.
93	///
94	/// Used, for example, in the benchmark for `claim_assets`.
95	fn get_asset() -> Asset;
96
97	/// Gets `n` distinct fungible assets handled by the `AssetTransactor`, preferring the most
98	/// expensive-to-deposit kind the chain supports. Used only in benchmarks.
99	///
100	/// The default ignores `n` and returns a single asset, so it measures a ~zero per-asset
101	/// slope: that is sound only for chains whose `AssetTransactor` handles one asset kind.
102	/// Chains that can deposit several MUST override it, otherwise `claim_assets` is
103	/// under-weighted; there is no generic default, since which ids are depositable is
104	/// runtime-specific.
105	fn get_assets(_n: u32) -> Assets {
106		Self::get_asset().into()
107	}
108
109	/// Wraps `calls` into a single call that dispatches all of them, e.g. `utility.batch`. Used
110	/// only by [`helpers::worst_case_weighable_message`].
111	///
112	/// If `None`, that worst case degrades to a single call and measures a far cheaper slope,
113	/// so chains with a batching pallet MUST implement this or the precompile that charges
114	/// `weigh_message` is under-charged.
115	fn batch_call(
116		_calls: Vec<<Self as crate::Config>::RuntimeCall>,
117	) -> Option<<Self as crate::Config>::RuntimeCall> {
118		None
119	}
120}
121
122// The `From<Call<T>>` bound lets `weigh_message` build its own worst-case payload; every
123// `construct_runtime!` runtime provides that conversion.
124#[benchmarks(where <T as crate::Config>::RuntimeCall: From<crate::Call<T>>)]
125mod benchmarks {
126	use super::*;
127
128	#[benchmark]
129	fn send() -> Result<(), BenchmarkError> {
130		let send_origin =
131			T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
132		if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() {
133			return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
134		}
135		let msg = Xcm(vec![ClearOrigin]);
136		let versioned_dest: VersionedLocation = T::reachable_dest()
137			.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?
138			.into();
139		let versioned_msg = VersionedXcm::from(msg);
140
141		// Ensure that origin can send to destination
142		// (e.g. setup delivery fees, ensure router setup, ...)
143		T::DeliveryHelper::ensure_successful_delivery(
144			&Default::default(),
145			&versioned_dest.clone().try_into().unwrap(),
146			FeeReason::ChargeFees,
147		);
148
149		#[extrinsic_call]
150		_(send_origin as RuntimeOrigin<T>, Box::new(versioned_dest), Box::new(versioned_msg));
151
152		Ok(())
153	}
154
155	#[benchmark]
156	fn teleport_assets() -> Result<(), BenchmarkError> {
157		let (asset, destination) = T::teleportable_asset_and_dest()
158			.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
159
160		let assets: Assets = asset.clone().into();
161
162		let caller: T::AccountId = whitelisted_caller();
163		let send_origin = RawOrigin::Signed(caller.clone());
164		let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
165			.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
166		if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner()))
167		{
168			return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
169		}
170
171		// Ensure that origin can send to destination
172		// (e.g. setup delivery fees, ensure router setup, ...)
173		let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
174			&origin_location,
175			&destination,
176			FeeReason::ChargeFees,
177		);
178
179		match &asset.fun {
180			Fungible(amount) => {
181				// Add transferred_amount to origin
182				let context =
183					XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
184				let asset_to_mint = Asset { fun: Fungible(*amount), id: asset.id.clone() };
185				let holdings = <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::mint_asset(
186					&asset_to_mint,
187					&context,
188				)
189				.map_err(|error| {
190					tracing::error!("Fungible asset couldn't be minted, error: {:?}", error);
191					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
192				})?;
193				<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
194					holdings,
195					&origin_location,
196					Some(&context),
197				)
198				.map_err(|error| {
199					tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error.1);
200					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
201				})?;
202			},
203			NonFungible(_instance) => {
204				let context =
205					XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
206				let holdings = <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::mint_asset(
207					&asset, &context,
208				)
209				.map_err(|error| {
210					tracing::error!("Nonfungible asset couldn't be minted, error: {:?}", error);
211					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
212				})?;
213				<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
214					holdings,
215					&origin_location,
216					Some(&context),
217				)
218				.map_err(|error| {
219					tracing::error!(
220						"Nonfungible asset couldn't be deposited, error: {:?}",
221						error.1
222					);
223					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
224				})?;
225			},
226		};
227
228		let recipient = [0u8; 32];
229		let versioned_dest: VersionedLocation = destination.into();
230		let versioned_beneficiary: VersionedLocation =
231			AccountId32 { network: None, id: recipient.into() }.into();
232		let versioned_assets: VersionedAssets = assets.into();
233
234		#[extrinsic_call]
235		_(
236			send_origin,
237			Box::new(versioned_dest),
238			Box::new(versioned_beneficiary),
239			Box::new(versioned_assets),
240			0,
241		);
242
243		Ok(())
244	}
245
246	#[benchmark]
247	fn reserve_transfer_assets() -> Result<(), BenchmarkError> {
248		let (asset, destination) = T::reserve_transferable_asset_and_dest()
249			.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
250
251		let assets: Assets = asset.clone().into();
252
253		let caller: T::AccountId = whitelisted_caller();
254		let send_origin = RawOrigin::Signed(caller.clone());
255		let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
256			.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
257		if !T::XcmReserveTransferFilter::contains(&(
258			origin_location.clone(),
259			assets.clone().into_inner(),
260		)) {
261			return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
262		}
263
264		// Ensure that origin can send to destination
265		// (e.g. setup delivery fees, ensure router setup, ...)
266		let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
267			&origin_location,
268			&destination,
269			FeeReason::ChargeFees,
270		);
271
272		match &asset.fun {
273			Fungible(amount) => {
274				// Add transferred_amount to origin
275				let context =
276					XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
277				let asset_to_mint = Asset { fun: Fungible(*amount), id: asset.id.clone() };
278				let holdings = <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::mint_asset(
279					&asset_to_mint,
280					&context,
281				)
282				.map_err(|error| {
283					tracing::error!("Fungible asset couldn't be minted, error: {:?}", error);
284					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
285				})?;
286				<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
287					holdings,
288					&origin_location,
289					Some(&context),
290				)
291				.map_err(|error| {
292					tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error.1);
293					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
294				})?;
295			},
296			NonFungible(_instance) => {
297				let context =
298					XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
299				let holdings = <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::mint_asset(
300					&asset, &context,
301				)
302				.map_err(|error| {
303					tracing::error!("Nonfungible asset couldn't be minted, error: {:?}", error);
304					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
305				})?;
306				<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(
307					holdings,
308					&origin_location,
309					Some(&context),
310				)
311				.map_err(|error| {
312					tracing::error!(
313						"Nonfungible asset couldn't be deposited, error: {:?}",
314						error.1
315					);
316					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
317				})?;
318			},
319		};
320
321		let recipient = [0u8; 32];
322		let versioned_dest: VersionedLocation = destination.clone().into();
323		let versioned_beneficiary: VersionedLocation =
324			AccountId32 { network: None, id: recipient.into() }.into();
325		let versioned_assets: VersionedAssets = assets.into();
326
327		#[extrinsic_call]
328		_(
329			send_origin,
330			Box::new(versioned_dest),
331			Box::new(versioned_beneficiary),
332			Box::new(versioned_assets),
333			0,
334		);
335
336		match &asset.fun {
337			Fungible(amount) => {
338				assert_ok!(<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::withdraw_asset(
339					&Asset { fun: Fungible(*amount), id: asset.id },
340					&destination,
341					None,
342				));
343			},
344			NonFungible(_instance) => {
345				assert_ok!(<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::withdraw_asset(
346					&asset,
347					&destination,
348					None,
349				));
350			},
351		};
352
353		Ok(())
354	}
355
356	#[benchmark]
357	fn transfer_assets() -> Result<(), BenchmarkError> {
358		let (assets, _fee_index, destination, verify_fn) = T::set_up_complex_asset_transfer()
359			.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
360		let caller: T::AccountId = whitelisted_caller();
361		let send_origin = RawOrigin::Signed(caller.clone());
362		let recipient = [0u8; 32];
363		let versioned_dest: VersionedLocation = destination.into();
364		let versioned_beneficiary: VersionedLocation =
365			AccountId32 { network: None, id: recipient.into() }.into();
366		let versioned_assets: VersionedAssets = assets.into();
367
368		// Ensure that origin can send to destination
369		// (e.g. setup delivery fees, ensure router setup, ...)
370		T::DeliveryHelper::ensure_successful_delivery(
371			&Default::default(),
372			&versioned_dest.clone().try_into().unwrap(),
373			FeeReason::ChargeFees,
374		);
375
376		#[extrinsic_call]
377		_(
378			send_origin,
379			Box::new(versioned_dest),
380			Box::new(versioned_beneficiary),
381			Box::new(versioned_assets),
382			0,
383			WeightLimit::Unlimited,
384		);
385
386		// run provided verification function
387		verify_fn();
388		Ok(())
389	}
390
391	#[benchmark]
392	fn execute() -> Result<(), BenchmarkError> {
393		let execute_origin =
394			T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
395		let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone())
396			.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
397		let msg = Xcm(vec![ClearOrigin]);
398		if !T::XcmExecuteFilter::contains(&(origin_location, msg.clone())) {
399			return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
400		}
401		let versioned_msg = VersionedXcm::from(msg);
402
403		#[extrinsic_call]
404		_(execute_origin as RuntimeOrigin<T>, Box::new(versioned_msg), Weight::MAX);
405
406		Ok(())
407	}
408
409	#[benchmark]
410	fn force_xcm_version() -> Result<(), BenchmarkError> {
411		let loc = T::reachable_dest()
412			.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
413		let xcm_version = 2;
414
415		#[extrinsic_call]
416		_(RawOrigin::Root, Box::new(loc), xcm_version);
417
418		Ok(())
419	}
420
421	#[benchmark]
422	fn force_default_xcm_version() {
423		#[extrinsic_call]
424		_(RawOrigin::Root, Some(2))
425	}
426
427	#[benchmark]
428	fn force_subscribe_version_notify() -> Result<(), BenchmarkError> {
429		let versioned_loc: VersionedLocation = T::reachable_dest()
430			.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?
431			.into();
432
433		// Ensure that origin can send to destination
434		// (e.g. setup delivery fees, ensure router setup, ...)
435		T::DeliveryHelper::ensure_successful_delivery(
436			&Default::default(),
437			&versioned_loc.clone().try_into().unwrap(),
438			FeeReason::ChargeFees,
439		);
440
441		#[extrinsic_call]
442		_(RawOrigin::Root, Box::new(versioned_loc));
443
444		Ok(())
445	}
446
447	#[benchmark]
448	fn force_unsubscribe_version_notify() -> Result<(), BenchmarkError> {
449		let loc = T::reachable_dest()
450			.ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
451		let versioned_loc: VersionedLocation = loc.clone().into();
452
453		// Ensure that origin can send to destination
454		// (e.g. setup delivery fees, ensure router setup, ...)
455		T::DeliveryHelper::ensure_successful_delivery(
456			&Default::default(),
457			&versioned_loc.clone().try_into().unwrap(),
458			FeeReason::ChargeFees,
459		);
460
461		let _ = crate::Pallet::<T>::request_version_notify(loc);
462
463		#[extrinsic_call]
464		_(RawOrigin::Root, Box::new(versioned_loc));
465
466		Ok(())
467	}
468
469	#[benchmark]
470	fn force_suspension() {
471		#[extrinsic_call]
472		_(RawOrigin::Root, true)
473	}
474
475	#[benchmark]
476	fn migrate_supported_version() {
477		let old_version = XCM_VERSION - 1;
478		let loc = VersionedLocation::from(Location::from(Parent));
479		SupportedVersion::<T>::insert(old_version, loc, old_version);
480
481		#[block]
482		{
483			crate::Pallet::<T>::lazy_migration(
484				VersionMigrationStage::MigrateSupportedVersion,
485				Weight::zero(),
486			);
487		}
488	}
489
490	#[benchmark]
491	fn migrate_version_notifiers() {
492		let old_version = XCM_VERSION - 1;
493		let loc = VersionedLocation::from(Location::from(Parent));
494		VersionNotifiers::<T>::insert(old_version, loc, 0);
495
496		#[block]
497		{
498			crate::Pallet::<T>::lazy_migration(
499				VersionMigrationStage::MigrateVersionNotifiers,
500				Weight::zero(),
501			);
502		}
503	}
504
505	#[benchmark]
506	fn already_notified_target() -> Result<(), BenchmarkError> {
507		let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
508			BenchmarkResult::from_weight(T::DbWeight::get().reads(1)),
509		))?;
510		let loc = VersionedLocation::from(loc);
511		let current_version = T::AdvertisedXcmVersion::get();
512		VersionNotifyTargets::<T>::insert(
513			current_version,
514			loc,
515			(0, Weight::zero(), current_version),
516		);
517
518		#[block]
519		{
520			crate::Pallet::<T>::lazy_migration(
521				VersionMigrationStage::NotifyCurrentTargets(None),
522				Weight::zero(),
523			);
524		}
525
526		Ok(())
527	}
528
529	#[benchmark]
530	fn notify_current_targets() -> Result<(), BenchmarkError> {
531		let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
532			BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)),
533		))?;
534		let loc = VersionedLocation::from(loc);
535		let current_version = T::AdvertisedXcmVersion::get();
536		let old_version = current_version - 1;
537		VersionNotifyTargets::<T>::insert(current_version, loc, (0, Weight::zero(), old_version));
538
539		#[block]
540		{
541			crate::Pallet::<T>::lazy_migration(
542				VersionMigrationStage::NotifyCurrentTargets(None),
543				Weight::zero(),
544			);
545		}
546
547		Ok(())
548	}
549
550	#[benchmark]
551	fn notify_target_migration_fail() {
552		let newer_xcm_version = xcm::prelude::XCM_VERSION;
553		let older_xcm_version = newer_xcm_version - 1;
554		let bad_location: Location = Plurality { id: BodyId::Unit, part: BodyPart::Voice }.into();
555		let bad_location = VersionedLocation::from(bad_location)
556			.into_version(older_xcm_version)
557			.expect("Version conversion should work");
558		let current_version = T::AdvertisedXcmVersion::get();
559		VersionNotifyTargets::<T>::insert(
560			current_version,
561			bad_location,
562			(0, Weight::zero(), current_version),
563		);
564
565		#[block]
566		{
567			crate::Pallet::<T>::lazy_migration(
568				VersionMigrationStage::MigrateAndNotifyOldTargets,
569				Weight::zero(),
570			);
571		}
572	}
573
574	#[benchmark]
575	fn migrate_version_notify_targets() {
576		let current_version = T::AdvertisedXcmVersion::get();
577		let old_version = current_version - 1;
578		let loc = VersionedLocation::from(Location::from(Parent));
579		VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), current_version));
580
581		#[block]
582		{
583			crate::Pallet::<T>::lazy_migration(
584				VersionMigrationStage::MigrateAndNotifyOldTargets,
585				Weight::zero(),
586			);
587		}
588	}
589
590	#[benchmark]
591	fn migrate_and_notify_old_targets() -> Result<(), BenchmarkError> {
592		let loc = T::reachable_dest().ok_or(BenchmarkError::Override(
593			BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)),
594		))?;
595		let loc = VersionedLocation::from(loc);
596		let old_version = T::AdvertisedXcmVersion::get() - 1;
597		VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), old_version));
598
599		#[block]
600		{
601			crate::Pallet::<T>::lazy_migration(
602				VersionMigrationStage::MigrateAndNotifyOldTargets,
603				Weight::zero(),
604			);
605		}
606
607		Ok(())
608	}
609
610	#[benchmark]
611	fn new_query() {
612		let responder = Location::from(Parent);
613		let timeout = 1u32.into();
614		let match_querier = Location::from(Here);
615
616		#[block]
617		{
618			crate::Pallet::<T>::new_query(responder, timeout, match_querier);
619		}
620	}
621
622	#[benchmark]
623	fn take_response() {
624		let responder = Location::from(Parent);
625		let timeout = 1u32.into();
626		let match_querier = Location::from(Here);
627		let query_id = crate::Pallet::<T>::new_query(responder, timeout, match_querier);
628		let infos = (0..xcm::v3::MaxPalletsInfo::get())
629			.map(|_| {
630				PalletInfo::new(
631					u32::MAX,
632					(0..xcm::v3::MaxPalletNameLen::get())
633						.map(|_| 97u8)
634						.collect::<Vec<_>>()
635						.try_into()
636						.unwrap(),
637					(0..xcm::v3::MaxPalletNameLen::get())
638						.map(|_| 97u8)
639						.collect::<Vec<_>>()
640						.try_into()
641						.unwrap(),
642					u32::MAX,
643					u32::MAX,
644					u32::MAX,
645				)
646				.unwrap()
647			})
648			.collect::<Vec<_>>();
649		crate::Pallet::<T>::expect_response(
650			query_id,
651			Response::PalletsInfo(infos.try_into().unwrap()),
652		);
653
654		#[block]
655		{
656			<crate::Pallet<T> as QueryHandler>::take_response(query_id);
657		}
658	}
659
660	#[benchmark]
661	fn claim_assets(n: Linear<1, { MAX_ITEMS_IN_ASSETS as u32 }>) -> Result<(), BenchmarkError> {
662		let claim_origin = RawOrigin::Signed(whitelisted_caller());
663		let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into())
664			.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
665		let assets = T::get_assets(n);
666		if (assets.len() as u32) < n {
667			tracing::warn!(
668				target: "xcm::benchmarking::pallet_xcm::claim_assets",
669				requested = n,
670				distinct = assets.len(),
671				"`get_assets` returned fewer distinct assets than requested; the weight will \
672				 have a ~zero per-asset slope. Chains that can deposit multiple distinct \
673				 assets must override `benchmarking::Config::get_assets`.",
674			);
675		}
676		let context = XcmContext { origin: None, message_id: [0u8; 32], topic: None };
677		// Trap all assets with a single `drop_assets` call: the trap is keyed on the hash
678		// of the whole asset set, so the claim must match it exactly.
679		let mut holding = AssetsInHolding::new();
680		for asset in assets.inner() {
681			let minted =
682				<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::mint_asset(asset, &context)
683					.map_err(|_| {
684						BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
685					})?;
686			holding.subsume_assets(minted);
687		}
688		crate::Pallet::<T>::drop_assets(&claim_location, holding, &context);
689		let versioned_assets = VersionedAssets::from(assets);
690
691		#[extrinsic_call]
692		_(
693			claim_origin,
694			Box::new(versioned_assets),
695			Box::new(VersionedLocation::from(claim_location)),
696		);
697
698		Ok(())
699	}
700
701	#[benchmark]
702	fn add_authorized_alias() -> Result<(), BenchmarkError> {
703		let who: T::AccountId = whitelisted_caller();
704		let origin = RawOrigin::Signed(who.clone());
705		let origin_location: VersionedLocation =
706			T::ExecuteXcmOrigin::try_origin(origin.clone().into())
707				.map_err(|_| {
708					tracing::error!(
709						target: "xcm::benchmarking::pallet_xcm::add_authorized_alias",
710						?origin,
711						"try_origin failed",
712					);
713					BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
714				})?
715				.into();
716
717		// Give some multiple of ED
718		let balance = T::ExistentialDeposit::get() * 1000000u32.into();
719		let _ =
720			<pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance);
721
722		let mut existing_aliases = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
723		// prepopulate list with `max-1` aliases to benchmark worst case
724		for i in 1..MaxAuthorizedAliases::get() {
725			let alias =
726				Location::new(1, [Parachain(i), AccountId32 { network: None, id: [42_u8; 32] }])
727					.into();
728			let aliaser = OriginAliaser { location: alias, expiry: None };
729			existing_aliases.try_push(aliaser).unwrap()
730		}
731		let footprint = aliasers_footprint(existing_aliases.len());
732		let ticket = TicketOf::<T>::new(&who, footprint).map_err(|e| {
733			tracing::error!(
734				target: "xcm::benchmarking::pallet_xcm::add_authorized_alias",
735				?who,
736				?footprint,
737				error=?e,
738				"could not create ticket",
739			);
740			BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))
741		})?;
742		let entry = AuthorizedAliasesEntry { aliasers: existing_aliases, ticket };
743		AuthorizedAliases::<T>::insert(&origin_location, entry);
744
745		// now benchmark adding new alias
746		let aliaser: VersionedLocation =
747			Location::new(1, [Parachain(1234), AccountId32 { network: None, id: [42_u8; 32] }])
748				.into();
749
750		#[extrinsic_call]
751		_(origin, Box::new(aliaser), None);
752
753		Ok(())
754	}
755
756	#[benchmark]
757	fn remove_authorized_alias() -> Result<(), BenchmarkError> {
758		let who: T::AccountId = whitelisted_caller();
759		let origin = RawOrigin::Signed(who.clone());
760		let error = BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX));
761		let origin_location =
762			T::ExecuteXcmOrigin::try_origin(origin.clone().into()).map_err(|_| {
763				tracing::error!(
764					target: "xcm::benchmarking::pallet_xcm::remove_authorized_alias",
765					?origin,
766					"try_origin failed",
767				);
768				error.clone()
769			})?;
770		// remove `network` from inner `AccountId32` for easier matching of automatic AccountId ->
771		// Location conversions.
772		let origin_location: VersionedLocation = match origin_location.unpack() {
773			(0, [AccountId32 { network: _, id }]) => {
774				Location::new(0, [AccountId32 { network: None, id: *id }]).into()
775			},
776			_ => {
777				tracing::error!(
778					target: "xcm::benchmarking::pallet_xcm::remove_authorized_alias",
779					?origin_location,
780					"unexpected origin failed",
781				);
782				return Err(error.clone());
783			},
784		};
785
786		// Give some multiple of ED
787		let balance = T::ExistentialDeposit::get() * 1000000u32.into();
788		let _ =
789			<pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance);
790
791		let mut existing_aliases = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
792		// prepopulate list with `max` aliases to benchmark worst case
793		for i in 1..MaxAuthorizedAliases::get() + 1 {
794			let alias =
795				Location::new(1, [Parachain(i), AccountId32 { network: None, id: [42_u8; 32] }])
796					.into();
797			let aliaser = OriginAliaser { location: alias, expiry: None };
798			existing_aliases.try_push(aliaser).unwrap()
799		}
800		let footprint = aliasers_footprint(existing_aliases.len());
801		let ticket = TicketOf::<T>::new(&who, footprint).map_err(|e| {
802			tracing::error!(
803				target: "xcm::benchmarking::pallet_xcm::remove_authorized_alias",
804				?who,
805				?footprint,
806				error=?e,
807				"could not create ticket",
808			);
809			error
810		})?;
811		let entry = AuthorizedAliasesEntry { aliasers: existing_aliases, ticket };
812		AuthorizedAliases::<T>::insert(&origin_location, entry);
813
814		// now benchmark removing an alias
815		let aliaser_to_remove: VersionedLocation =
816			Location::new(1, [Parachain(1), AccountId32 { network: None, id: [42_u8; 32] }]).into();
817
818		#[extrinsic_call]
819		_(origin, Box::new(aliaser_to_remove));
820
821		Ok(())
822	}
823
824	/// Decoding and weighing a caller-supplied message of `n` bytes.
825	///
826	/// The decode is measured, and goes through the precompile's entry point, because the
827	/// precompile charges this weight before decoding.
828	///
829	/// `n` stops at [`MAX_WEIGHABLE_BLOB_BYTES`] rather than [`MAX_XCM_BLOB_BYTES`].
830	#[benchmark]
831	fn weigh_message(n: Linear<0, MAX_WEIGHABLE_BLOB_BYTES>) -> Result<(), BenchmarkError> {
832		let bytes = helpers::worst_case_weighable_message::<T>(n);
833
834		#[block]
835		{
836			let decoded =
837				VersionedXcm::<<T as crate::Config>::RuntimeCall>::
838					decode_all_with_mem_and_depth_limit(&mut &bytes[..])
839					.expect("blob was just built by `worst_case_weighable_message`; qed");
840			let mut message: Xcm<<T as crate::Config>::RuntimeCall> =
841				decoded.try_into().expect("blob was built at the latest version; qed");
842			// A message this large may legitimately exceed limits; finding that out is the cost
843			// being measured.
844			let _ = <T as crate::Config>::Weigher::weight(&mut message, Weight::MAX);
845		}
846
847		Ok(())
848	}
849
850	/// Only decoding, not weighing, a caller-supplied message of `n` bytes.
851	#[benchmark]
852	fn decode_xcm(n: Linear<0, MAX_XCM_BLOB_BYTES>) -> Result<(), BenchmarkError> {
853		let bytes = helpers::worst_case_decodable_blob(n);
854
855		#[block]
856		{
857			let _ = VersionedXcm::<()>::decode_all_with_mem_and_depth_limit(&mut &bytes[..]);
858		}
859
860		Ok(())
861	}
862
863	impl_benchmark_test_suite!(
864		Pallet,
865		crate::mock::new_test_ext_with_balances(Vec::new()),
866		crate::mock::Test
867	);
868}
869
870#[cfg(test)]
871mod tests {
872	use super::*;
873	use crate::mock::Test;
874
875	/// A worst case that does not decode measures nothing, and the shared instruction budget
876	/// makes that easy to trip by accident.
877	#[test]
878	fn worst_case_weighable_message_decodes() {
879		for target_bytes in [0, 1, 1024, MAX_WEIGHABLE_BLOB_BYTES] {
880			let bytes = helpers::worst_case_weighable_message::<Test>(target_bytes);
881			assert!(
882				bytes.len() >= target_bytes as usize,
883				"undershooting the target would under-charge",
884			);
885			VersionedXcm::<<Test as crate::Config>::RuntimeCall>::decode_all_with_mem_and_depth_limit(
886				&mut &bytes[..],
887			)
888			.expect("worst case must decode, otherwise the benchmark measures nothing");
889		}
890	}
891}
892
893pub mod helpers {
894	use super::*;
895
896	/// The worst case for `WeightInfo::weigh_message`: a `Transact` carrying
897	/// `batch_call([pallet_xcm.execute(Xcm([])); N])`, encoded, of at least `target_bytes` bytes.
898	///
899	/// A `Transact` payload resolving to a local call is decoded eagerly, and weighing it recurses
900	/// `get_dispatch_info()` over every batched call, so both costs scale with the number of calls
901	/// the blob can hold. The worst case therefore spends `target_bytes` on as many calls as it
902	/// fits, leaving the inner messages empty.
903	///
904	/// Empty also keeps the blob decodable: `MAX_INSTRUCTIONS_TO_DECODE` is one budget shared by
905	/// every nesting level, so instructions inside a `Transact` spend the outer allowance.
906	///
907	/// Keep `target_bytes` at or below `MAX_WEIGHABLE_BLOB_BYTES`.
908	pub fn worst_case_weighable_message<T: Config>(target_bytes: u32) -> Vec<u8>
909	where
910		<T as crate::Config>::RuntimeCall: From<crate::Call<T>>,
911	{
912		let inner = Xcm::<<T as crate::Config>::RuntimeCall>(Vec::new());
913		let one: <T as crate::Config>::RuntimeCall = crate::Call::<T>::execute {
914			message: Box::new(VersionedXcm::from(inner)),
915			max_weight: Weight::zero(),
916		}
917		.into();
918		let count = (target_bytes as usize).div_ceil(one.encode().len());
919
920		let call = T::batch_call(vec![one.clone(); count]).unwrap_or_else(|| {
921			tracing::warn!(
922				target: "xcm::benchmarking::pallet_xcm::weigh_message",
923				"`batch_call` is not implemented, so the measured per-byte slope is far below \
924				 the real worst case. Chains with a batching pallet must implement \
925				 `benchmarking::Config::batch_call`.",
926			);
927			one
928		});
929
930		let message = Xcm::<<T as crate::Config>::RuntimeCall>(vec![Transact {
931			origin_kind: OriginKind::SovereignAccount,
932			fallback_max_weight: None,
933			call: call.encode().into(),
934		}]);
935		VersionedXcm::from(message).encode()
936	}
937
938	/// The worst case for `WeightInfo::decode_xcm`: an encoded `VersionedXcm<()>` of at least
939	/// `target_bytes` bytes, as expensive to decode as the format allows.
940	///
941	/// `MAX_INSTRUCTIONS_TO_DECODE` caps the instruction count, so bytes past a hundred have to
942	/// come from payloads. The densest decodable one is `ReserveAssetDeposited` carrying
943	/// max-length asset ids, where nearly every byte is a nested enum or bounded-vec decode
944	/// rather than a byte copy.
945	pub fn worst_case_decodable_blob(target_bytes: u32) -> Vec<u8> {
946		// `index` keeps the ids distinct so that `Assets` does not deduplicate them.
947		let dense_asset = |index: u32| -> Asset {
948			let mut data = [0u8; 32];
949			data[..4].copy_from_slice(&index.to_le_bytes());
950			Asset {
951				id: AssetId(Location::new(1, [GeneralKey { length: 32, data }; 8])),
952				fun: Fungible(u128::MAX),
953			}
954		};
955
956		// Round up; undershooting `target_bytes` would under-charge.
957		let asset_len = dense_asset(0).encode().len();
958		let count = (target_bytes as usize).div_ceil(asset_len);
959		debug_assert!(
960			count <= MAX_INSTRUCTIONS_TO_DECODE as usize * MAX_ITEMS_IN_ASSETS,
961			"more assets than the instruction limit can carry; the blob would not decode",
962		);
963		let assets = (0..count).map(|index| dense_asset(index as u32)).collect::<Vec<_>>();
964
965		let instructions = assets
966			.chunks(MAX_ITEMS_IN_ASSETS)
967			.map(|chunk| ReserveAssetDeposited(chunk.to_vec().into()))
968			.collect::<Vec<Instruction<()>>>();
969		VersionedXcm::from(Xcm::<()>(instructions)).encode()
970	}
971
972	pub fn native_teleport_as_asset_transfer<T>(
973		native_asset_location: Location,
974		destination: Location,
975	) -> Option<(Assets, u32, Location, Box<dyn FnOnce()>)>
976	where
977		T: Config + pallet_balances::Config,
978		u128: From<<T as pallet_balances::Config>::Balance>,
979	{
980		// Relay/native token can be teleported to/from AH.
981		let amount = T::ExistentialDeposit::get() * 100u32.into();
982		let assets: Assets =
983			Asset { fun: Fungible(amount.into()), id: AssetId(native_asset_location) }.into();
984		let fee_index = 0u32;
985
986		// Give some multiple of transferred amount
987		let balance = amount * 10u32.into();
988		let who = whitelisted_caller();
989		let _ =
990			<pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance);
991		// verify initial balance
992		assert_eq!(pallet_balances::Pallet::<T>::free_balance(&who), balance);
993
994		// verify transferred successfully
995		let verify = Box::new(move || {
996			// verify balance after transfer, decreased by transferred amount (and delivery fees)
997			assert!(pallet_balances::Pallet::<T>::free_balance(&who) <= balance - amount);
998		});
999		Some((assets, fee_index, destination, verify))
1000	}
1001}