referrerpolicy=no-referrer-when-downgrade

pallet_xcm_benchmarks/fungible/
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 crate::{account_and_location, new_executor, AssetTransactorOf, EnsureDelivery, XcmCallOf};
19use alloc::{vec, vec::Vec};
20use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError, BenchmarkResult};
21use frame_support::{
22	pallet_prelude::Get,
23	traits::fungible::{Inspect, Mutate},
24	weights::Weight,
25	BoundedVec,
26};
27use sp_runtime::traits::Bounded;
28use xcm::latest::{prelude::*, AssetTransferFilter, MAX_ITEMS_IN_ASSETS};
29use xcm_executor::{
30	traits::{ConvertLocation, FeeReason, TransactAsset},
31	AssetsInHolding,
32};
33
34/// Helper function to convert Assets to AssetsInHolding by minting each asset.
35/// This is used for benchmark setup where we need to create imbalances.
36fn assets_to_holding<T: crate::Config>(assets: &Assets) -> Result<AssetsInHolding, XcmError> {
37	let context = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
38	let mut holding = AssetsInHolding::new();
39	for asset in assets.inner() {
40		let minted = <AssetTransactorOf<T>>::mint_asset(asset, &context)?;
41		holding.subsume_assets(minted);
42	}
43	Ok(holding)
44}
45
46benchmarks_instance_pallet! {
47	where_clause { where
48		<
49			<
50				T::TransactAsset
51				as
52				Inspect<T::AccountId>
53			>::Balance
54			as
55			TryInto<u128>
56		>::Error: core::fmt::Debug,
57	}
58
59	withdraw_asset {
60		let (sender_account, sender_location) = account_and_location::<T>(1);
61		let worst_case_holding = T::worst_case_holding(0);
62		let asset = T::get_asset();
63
64		let context = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
65		let holdings = <AssetTransactorOf<T>>::mint_asset(&asset, &context).unwrap();
66		<AssetTransactorOf<T>>::deposit_asset(holdings, &sender_location, Some(&context)).unwrap();
67
68		let mut executor = new_executor::<T>(sender_location);
69		executor.set_holding(worst_case_holding);
70		let instruction = Instruction::<XcmCallOf<T>>::WithdrawAsset(vec![asset.clone()].into());
71		let xcm = Xcm(vec![instruction]);
72	}: {
73		executor.bench_process(xcm)?;
74	} verify {
75		assert!(executor.holding().ensure_contains(&vec![asset].into()).is_ok());
76	}
77
78	transfer_asset {
79		let (sender_account, sender_location) = account_and_location::<T>(1);
80		let asset = T::get_asset();
81		let assets: Assets = vec![asset.clone()].into();
82		// this xcm doesn't use holding
83
84		let dest_location = T::valid_destination()?;
85		let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
86
87		let context = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
88		let holdings = <AssetTransactorOf<T>>::mint_asset(&asset, &context).unwrap();
89		<AssetTransactorOf<T>>::deposit_asset(holdings, &sender_location, Some(&context)).unwrap();
90		// We deposit the asset twice so we have enough for ED after transferring
91		let holdings = <AssetTransactorOf<T>>::mint_asset(&asset, &context).unwrap();
92		<AssetTransactorOf<T>>::deposit_asset(holdings, &sender_location, Some(&context)).unwrap();
93
94		let mut executor = new_executor::<T>(sender_location);
95		let instruction = Instruction::TransferAsset { assets, beneficiary: dest_location };
96		let xcm = Xcm(vec![instruction]);
97	}: {
98		executor.bench_process(xcm)?;
99	} verify {
100	}
101
102	transfer_reserve_asset {
103		let (sender_account, sender_location) = account_and_location::<T>(1);
104		let dest_location = T::valid_destination()?;
105		let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
106
107		let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
108			&sender_location,
109			&dest_location,
110			FeeReason::TransferReserveAsset
111		);
112
113		let asset = T::get_asset();
114		let context = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
115		let holdings = <AssetTransactorOf<T>>::mint_asset(&asset, &context).unwrap();
116		<AssetTransactorOf<T>>::deposit_asset(holdings, &sender_location, Some(&context)).unwrap();
117		// We deposit the asset twice so we have enough for ED after transferring
118		let holdings = <AssetTransactorOf<T>>::mint_asset(&asset, &context).unwrap();
119		<AssetTransactorOf<T>>::deposit_asset(holdings, &sender_location, Some(&context)).unwrap();
120		let assets: Assets = vec![asset].into();
121
122		let mut executor = new_executor::<T>(sender_location);
123		if let Some(expected_fees_mode) = expected_fees_mode {
124			executor.set_fees_mode(expected_fees_mode);
125		}
126		if let Some(expected_assets_in_holding) = expected_assets_in_holding {
127			// Mint real assets for delivery fees and add to holding
128			executor.set_holding(assets_to_holding::<T>(&expected_assets_in_holding).unwrap());
129		}
130
131		let instruction = Instruction::TransferReserveAsset {
132			assets,
133			dest: dest_location,
134			xcm: Xcm::new()
135		};
136		let xcm = Xcm(vec![instruction]);
137	}: {
138		executor.bench_process(xcm)?;
139	} verify {
140		// TODO: Check sender queue is not empty. #4426
141	}
142
143	reserve_asset_deposited {
144		let (trusted_reserve, transferable_reserve_asset) = T::TrustedReserve::get()
145			.ok_or(BenchmarkError::Override(
146				BenchmarkResult::from_weight(Weight::MAX)
147			))?;
148
149		let assets: Assets = vec![ transferable_reserve_asset ].into();
150
151		let mut executor = new_executor::<T>(trusted_reserve);
152		let instruction = Instruction::ReserveAssetDeposited(assets.clone());
153		let xcm = Xcm(vec![instruction]);
154	}: {
155		executor.bench_process(xcm)?;
156	} verify {
157		assert!(executor.holding().ensure_contains(&assets).is_ok());
158	}
159
160	initiate_reserve_withdraw {
161		let (sender_account, sender_location) = account_and_location::<T>(1);
162		let reserve = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
163
164		let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
165			&sender_location,
166			&reserve,
167			FeeReason::InitiateReserveWithdraw,
168		);
169		let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
170
171		// generate holding and add possible required fees
172		let holding = if let Some(expected_assets_in_holding) = expected_assets_in_holding {
173			let mut holding = T::worst_case_holding(1 + expected_assets_in_holding.len() as u32);
174			// Mint real assets for delivery fees and merge into holding
175			let real_assets = assets_to_holding::<T>(&expected_assets_in_holding).unwrap();
176			holding.subsume_assets(real_assets);
177			holding
178		} else {
179			T::worst_case_holding(1)
180		};
181
182		// Build Assets descriptor from AssetsInHolding for the instruction (before consuming holding)
183		let withdraw_assets: Assets = {
184			let mut assets = Vec::new();
185			// Add fungible assets up to MAX_ITEMS_IN_ASSETS
186			for (asset_id, imbalance) in holding.fungible.iter().take(MAX_ITEMS_IN_ASSETS) {
187				assets.push(Asset {
188					id: asset_id.clone(),
189					fun: Fungible(imbalance.amount()),
190				});
191			}
192			// Add non-fungible assets if we haven't hit the limit
193			let remaining = MAX_ITEMS_IN_ASSETS.saturating_sub(assets.len());
194			for (asset_id, instance) in holding.non_fungible.iter().take(remaining) {
195				assets.push(Asset {
196					id: asset_id.clone(),
197					fun: NonFungible(*instance),
198				});
199			}
200			assets.into()
201		};
202
203		let mut executor = new_executor::<T>(sender_location);
204		executor.set_holding(holding);
205		if let Some(expected_fees_mode) = expected_fees_mode {
206			executor.set_fees_mode(expected_fees_mode);
207		}
208
209		let instruction = Instruction::InitiateReserveWithdraw {
210			// Worst case is looking through all holdings for every asset explicitly - respecting the limit `MAX_ITEMS_IN_ASSETS`.
211			assets: Definite(withdraw_assets),
212			reserve,
213			xcm: Xcm(vec![])
214		};
215		let xcm = Xcm(vec![instruction]);
216	}: {
217		executor.bench_process(xcm)?;
218	} verify {
219		// Check we charged the delivery fees
220		assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
221		// The execute completing successfully is as good as we can check.
222		// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
223	}
224
225	receive_teleported_asset {
226		// If there is no trusted teleporter, then we skip this benchmark.
227		let (trusted_teleporter, teleportable_asset) = T::TrustedTeleporter::get()
228			.ok_or(BenchmarkError::Skip)?;
229
230		if let Some((checked_account, _)) = T::CheckedAccount::get() {
231			T::TransactAsset::mint_into(
232				&checked_account,
233				<
234					T::TransactAsset
235					as
236					Inspect<T::AccountId>
237				>::Balance::max_value() / 2u32.into(),
238			)?;
239		}
240
241		let assets: Assets = vec![ teleportable_asset ].into();
242
243		let mut executor = new_executor::<T>(trusted_teleporter);
244		let instruction = Instruction::ReceiveTeleportedAsset(assets.clone());
245		let xcm = Xcm(vec![instruction]);
246	}: {
247		executor.bench_process(xcm).map_err(|_| {
248			BenchmarkError::Override(
249				BenchmarkResult::from_weight(Weight::MAX)
250			)
251		})?;
252	} verify {
253		assert!(executor.holding().ensure_contains(&assets).is_ok());
254	}
255
256	deposit_asset {
257		let asset = T::get_asset();
258		let mut holding = T::worst_case_holding(1);
259
260		// Add our asset to the holding.
261		let real_asset = assets_to_holding::<T>(&vec![asset.clone()].into()).unwrap();
262		holding.subsume_assets(real_asset);
263
264		// our dest must have no balance initially.
265		let dest_location = T::valid_destination()?;
266		let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
267
268		// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
269		let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
270			&Default::default(),
271			&dest_location,
272			FeeReason::ChargeFees,
273		);
274
275		let mut executor = new_executor::<T>(Default::default());
276		executor.set_holding(holding);
277		let instruction = Instruction::<XcmCallOf<T>>::DepositAsset {
278			assets: asset.into(),
279			beneficiary: dest_location,
280		};
281		let xcm = Xcm(vec![instruction]);
282	}: {
283		executor.bench_process(xcm)?;
284	} verify {
285	}
286
287	deposit_reserve_asset {
288		let asset = T::get_asset();
289		let mut holding = T::worst_case_holding(1);
290
291		// Add our asset to the holding.
292		let real_asset = assets_to_holding::<T>(&vec![asset.clone()].into()).unwrap();
293		holding.subsume_assets(real_asset);
294
295		// our dest must have no balance initially.
296		let dest_location = T::valid_destination()?;
297		let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
298
299		// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
300		let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
301			&Default::default(),
302			&dest_location,
303			FeeReason::ChargeFees,
304		);
305
306		let mut executor = new_executor::<T>(Default::default());
307		executor.set_holding(holding);
308		let instruction = Instruction::<XcmCallOf<T>>::DepositReserveAsset {
309			assets: asset.into(),
310			dest: dest_location,
311			xcm: Xcm::new(),
312		};
313		let xcm = Xcm(vec![instruction]);
314	}: {
315		executor.bench_process(xcm)?;
316	} verify {
317	}
318
319	initiate_teleport {
320		let asset = T::get_asset();
321		let mut holding = T::worst_case_holding(0);
322
323		// Add our asset to the holding.
324		let real_asset = assets_to_holding::<T>(&vec![asset.clone()].into()).unwrap();
325		holding.subsume_assets(real_asset);
326
327		let dest_location =  T::valid_destination()?;
328
329		// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
330		let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
331			&Default::default(),
332			&dest_location,
333			FeeReason::ChargeFees,
334		);
335
336		let mut executor = new_executor::<T>(Default::default());
337		executor.set_holding(holding);
338		let instruction = Instruction::<XcmCallOf<T>>::InitiateTeleport {
339			assets: asset.into(),
340			dest: dest_location,
341			xcm: Xcm::new(),
342		};
343		let xcm = Xcm(vec![instruction]);
344	}: {
345		executor.bench_process(xcm)?;
346	} verify {
347	}
348
349	initiate_transfer {
350		let (sender_account, sender_location) = account_and_location::<T>(1);
351		let asset = T::get_asset();
352		let mut holding = T::worst_case_holding(1);
353		let dest_location =  T::valid_destination()?;
354
355		// Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...)
356		let (_, _) = T::DeliveryHelper::ensure_successful_delivery(
357			&sender_location,
358			&dest_location,
359			FeeReason::ChargeFees,
360		);
361
362		// Add our asset to the holding.
363		let real_asset = assets_to_holding::<T>(&vec![asset.clone()].into()).unwrap();
364		holding.subsume_assets(real_asset);
365
366		let mut executor = new_executor::<T>(sender_location);
367		executor.set_holding(holding);
368		let instruction = Instruction::<XcmCallOf<T>>::InitiateTransfer {
369			destination: dest_location,
370			// ReserveDeposit is the most expensive filter.
371			remote_fees: Some(AssetTransferFilter::ReserveDeposit(asset.clone().into())),
372			// It's more expensive if we reanchor the origin.
373			preserve_origin: true,
374			assets: BoundedVec::truncate_from(vec![AssetTransferFilter::ReserveDeposit(asset.into())]),
375			remote_xcm: Xcm::new(),
376		};
377		let xcm = Xcm(vec![instruction]);
378	}: {
379		executor.bench_process(xcm)?;
380	} verify {
381	}
382
383	impl_benchmark_test_suite!(
384		Pallet,
385		crate::fungible::mock::new_test_ext(),
386		crate::fungible::mock::Test
387	);
388}