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