referrerpolicy=no-referrer-when-downgrade

frame_support_procedural/
benchmark.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Home of the parsing and expansion code for the new pallet benchmarking syntax
19
20use derive_syn_parse::Parse;
21use frame_support_procedural_tools::generate_access_from_frame_or_crate;
22use proc_macro::TokenStream;
23use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
24use quote::{quote, quote_spanned, ToTokens};
25use syn::{
26	parse::{Nothing, ParseStream},
27	parse_quote,
28	punctuated::Punctuated,
29	spanned::Spanned,
30	token::{Comma, Gt, Lt, PathSep},
31	Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, Pat, Path,
32	PathArguments, PathSegment, Result, ReturnType, Signature, Stmt, Token, Type, TypePath,
33	Visibility, WhereClause, WherePredicate,
34};
35
36mod keywords {
37	use syn::custom_keyword;
38
39	custom_keyword!(benchmark);
40	custom_keyword!(benchmarks);
41	custom_keyword!(block);
42	custom_keyword!(extra);
43	custom_keyword!(pov_mode);
44	custom_keyword!(extrinsic_call);
45	custom_keyword!(skip_meta);
46	custom_keyword!(BenchmarkError);
47	custom_keyword!(Result);
48	custom_keyword!(MaxEncodedLen);
49	custom_keyword!(Measured);
50	custom_keyword!(Ignored);
51
52	pub const BENCHMARK_TOKEN: &str = stringify!(benchmark);
53	pub const BENCHMARKS_TOKEN: &str = stringify!(benchmarks);
54}
55
56/// This represents the raw parsed data for a param definition such as `x: Linear<10, 20>`.
57#[derive(Clone)]
58struct ParamDef {
59	name: String,
60	_typ: Type,
61	start: syn::GenericArgument,
62	end: syn::GenericArgument,
63}
64
65/// Allows easy parsing of the `<10, 20>` component of `x: Linear<10, 20>`.
66#[derive(Parse)]
67struct RangeArgs {
68	_lt_token: Lt,
69	start: syn::GenericArgument,
70	_comma: Comma,
71	end: syn::GenericArgument,
72	_trailing_comma: Option<Comma>,
73	_gt_token: Gt,
74}
75
76#[derive(Clone, Debug)]
77struct BenchmarkAttrs {
78	skip_meta: bool,
79	extra: bool,
80	pov_mode: Option<PovModeAttr>,
81}
82
83/// Represents a single benchmark option
84enum BenchmarkAttr {
85	Extra,
86	SkipMeta,
87	/// How the PoV should be measured.
88	PoV(PovModeAttr),
89}
90
91impl syn::parse::Parse for PovModeAttr {
92	fn parse(input: ParseStream) -> Result<Self> {
93		let _pov: keywords::pov_mode = input.parse()?;
94		let _eq: Token![=] = input.parse()?;
95		let root = PovEstimationMode::parse(input)?;
96
97		let mut maybe_content = None;
98		let _ = || -> Result<()> {
99			let content;
100			syn::braced!(content in input);
101			maybe_content = Some(content);
102			Ok(())
103		}();
104
105		let per_key = match maybe_content {
106			Some(content) => {
107				let per_key = Punctuated::<PovModeKeyAttr, Token![,]>::parse_terminated(&content)?;
108				per_key.into_iter().collect()
109			},
110			None => Vec::new(),
111		};
112
113		Ok(Self { root, per_key })
114	}
115}
116
117impl syn::parse::Parse for BenchmarkAttr {
118	fn parse(input: ParseStream) -> Result<Self> {
119		let lookahead = input.lookahead1();
120		if lookahead.peek(keywords::extra) {
121			let _extra: keywords::extra = input.parse()?;
122			Ok(BenchmarkAttr::Extra)
123		} else if lookahead.peek(keywords::skip_meta) {
124			let _skip_meta: keywords::skip_meta = input.parse()?;
125			Ok(BenchmarkAttr::SkipMeta)
126		} else if lookahead.peek(keywords::pov_mode) {
127			PovModeAttr::parse(input).map(BenchmarkAttr::PoV)
128		} else {
129			Err(lookahead.error())
130		}
131	}
132}
133
134/// A `#[pov_mode = .. { .. }]` attribute.
135#[derive(Debug, Clone)]
136struct PovModeAttr {
137	/// The root mode for this benchmarks.
138	root: PovEstimationMode,
139	/// The pov-mode for a specific key. This overwrites `root` for this key.
140	per_key: Vec<PovModeKeyAttr>,
141}
142
143/// A single key-value pair inside the `{}` of a `#[pov_mode = .. { .. }]` attribute.
144#[derive(Debug, Clone, derive_syn_parse::Parse)]
145struct PovModeKeyAttr {
146	/// A specific storage key for which to set the PoV mode.
147	key: Path,
148	_underscore: Token![:],
149	/// The PoV mode for this key.
150	mode: PovEstimationMode,
151}
152
153/// How the PoV should be estimated.
154#[derive(Debug, Eq, PartialEq, Clone, Copy)]
155pub enum PovEstimationMode {
156	/// Use the maximal encoded length as provided by [`codec::MaxEncodedLen`].
157	MaxEncodedLen,
158	/// Measure the accessed value size in the pallet benchmarking and add some trie overhead.
159	Measured,
160	/// Do not estimate the PoV size for this storage item or benchmark.
161	Ignored,
162}
163
164impl syn::parse::Parse for PovEstimationMode {
165	fn parse(input: ParseStream) -> Result<Self> {
166		let lookahead = input.lookahead1();
167		if lookahead.peek(keywords::MaxEncodedLen) {
168			let _max_encoded_len: keywords::MaxEncodedLen = input.parse()?;
169			return Ok(PovEstimationMode::MaxEncodedLen)
170		} else if lookahead.peek(keywords::Measured) {
171			let _measured: keywords::Measured = input.parse()?;
172			return Ok(PovEstimationMode::Measured)
173		} else if lookahead.peek(keywords::Ignored) {
174			let _ignored: keywords::Ignored = input.parse()?;
175			return Ok(PovEstimationMode::Ignored)
176		} else {
177			return Err(lookahead.error())
178		}
179	}
180}
181
182impl ToString for PovEstimationMode {
183	fn to_string(&self) -> String {
184		match self {
185			PovEstimationMode::MaxEncodedLen => "MaxEncodedLen".into(),
186			PovEstimationMode::Measured => "Measured".into(),
187			PovEstimationMode::Ignored => "Ignored".into(),
188		}
189	}
190}
191
192impl quote::ToTokens for PovEstimationMode {
193	fn to_tokens(&self, tokens: &mut TokenStream2) {
194		match self {
195			PovEstimationMode::MaxEncodedLen => tokens.extend(quote!(MaxEncodedLen)),
196			PovEstimationMode::Measured => tokens.extend(quote!(Measured)),
197			PovEstimationMode::Ignored => tokens.extend(quote!(Ignored)),
198		}
199	}
200}
201
202impl syn::parse::Parse for BenchmarkAttrs {
203	fn parse(input: ParseStream) -> syn::Result<Self> {
204		let mut extra = false;
205		let mut skip_meta = false;
206		let mut pov_mode = None;
207		let args = Punctuated::<BenchmarkAttr, Token![,]>::parse_terminated(&input)?;
208
209		for arg in args.into_iter() {
210			match arg {
211				BenchmarkAttr::Extra => {
212					if extra {
213						return Err(input.error("`extra` can only be specified once"))
214					}
215					extra = true;
216				},
217				BenchmarkAttr::SkipMeta => {
218					if skip_meta {
219						return Err(input.error("`skip_meta` can only be specified once"))
220					}
221					skip_meta = true;
222				},
223				BenchmarkAttr::PoV(mode) => {
224					if pov_mode.is_some() {
225						return Err(input.error("`pov_mode` can only be specified once"))
226					}
227					pov_mode = Some(mode);
228				},
229			}
230		}
231		Ok(BenchmarkAttrs { extra, skip_meta, pov_mode })
232	}
233}
234
235/// Represents the parsed extrinsic call for a benchmark
236#[derive(Clone)]
237enum BenchmarkCallDef {
238	ExtrinsicCall { origin: Expr, expr_call: ExprCall, attr_span: Span }, // #[extrinsic_call]
239	Block { block: ExprBlock, attr_span: Span },                          // #[block]
240}
241
242impl BenchmarkCallDef {
243	/// Returns the `span()` for attribute
244	fn attr_span(&self) -> Span {
245		match self {
246			BenchmarkCallDef::ExtrinsicCall { origin: _, expr_call: _, attr_span } => *attr_span,
247			BenchmarkCallDef::Block { block: _, attr_span } => *attr_span,
248		}
249	}
250}
251
252/// Represents a parsed `#[benchmark]` or `#[instance_benchmark]` item.
253#[derive(Clone)]
254struct BenchmarkDef {
255	params: Vec<ParamDef>,
256	setup_stmts: Vec<Stmt>,
257	call_def: BenchmarkCallDef,
258	verify_stmts: Vec<Stmt>,
259	last_stmt: Option<Stmt>,
260	fn_sig: Signature,
261	fn_vis: Visibility,
262	fn_attrs: Vec<Attribute>,
263}
264
265/// used to parse something compatible with `Result<T, E>`
266#[derive(Parse)]
267struct ResultDef {
268	_result_kw: keywords::Result,
269	_lt: Token![<],
270	unit: Type,
271	_comma: Comma,
272	e_type: TypePath,
273	_gt: Token![>],
274}
275
276/// Ensures that `ReturnType` is a `Result<(), BenchmarkError>`, if specified
277fn ensure_valid_return_type(item_fn: &ItemFn) -> Result<()> {
278	if let ReturnType::Type(_, typ) = &item_fn.sig.output {
279		let non_unit = |span| return Err(Error::new(span, "expected `()`"));
280		let Type::Path(TypePath { path, qself: _ }) = &**typ else {
281			return Err(Error::new(
282					typ.span(),
283					"Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions",
284				))
285		};
286		let seg = path
287			.segments
288			.last()
289			.expect("to be parsed as a TypePath, it must have at least one segment; qed");
290		let res: ResultDef = syn::parse2(seg.to_token_stream())?;
291		// ensure T in Result<T, E> is ()
292		let Type::Tuple(tup) = res.unit else { return non_unit(res.unit.span()) };
293		if !tup.elems.is_empty() {
294			return non_unit(tup.span())
295		}
296		let TypePath { path, qself: _ } = res.e_type;
297		let seg = path
298			.segments
299			.last()
300			.expect("to be parsed as a TypePath, it must have at least one segment; qed");
301		syn::parse2::<keywords::BenchmarkError>(seg.to_token_stream())?;
302	}
303	Ok(())
304}
305
306/// Ensure that the passed statements do not contain any forbidden variable names
307fn ensure_no_forbidden_variable_names(stmts: &[Stmt]) -> Result<()> {
308	const FORBIDDEN_VAR_NAMES: [&str; 2] = ["recording", "verify"];
309	for stmt in stmts {
310		let Stmt::Local(l) = stmt else { continue };
311		let Pat::Ident(ident) = &l.pat else { continue };
312		if FORBIDDEN_VAR_NAMES.contains(&ident.ident.to_string().as_str()) {
313			return Err(Error::new(
314				ident.span(),
315				format!(
316					"Variables {FORBIDDEN_VAR_NAMES:?} are reserved for benchmarking internals.",
317				),
318			));
319		}
320	}
321	Ok(())
322}
323
324/// Parses params such as `x: Linear<0, 1>`
325fn parse_params(item_fn: &ItemFn) -> Result<Vec<ParamDef>> {
326	let mut params: Vec<ParamDef> = Vec::new();
327	for arg in &item_fn.sig.inputs {
328		let invalid_param = |span| {
329			return Err(Error::new(
330				span,
331				"Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.",
332			))
333		};
334
335		let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
336		let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
337
338		// check param name
339		let var_span = ident.span();
340		let invalid_param_name = || {
341			return Err(Error::new(
342					var_span,
343					"Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.",
344				));
345		};
346		let name = ident.ident.to_token_stream().to_string();
347		if name.len() > 1 {
348			return invalid_param_name()
349		};
350		let Some(name_char) = name.chars().next() else { return invalid_param_name() };
351		if !name_char.is_alphabetic() || !name_char.is_lowercase() {
352			return invalid_param_name()
353		}
354
355		// parse type
356		let typ = &*arg.ty;
357		let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
358		let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
359		let args = segment.arguments.to_token_stream().into();
360		let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
361
362		params.push(ParamDef { name, _typ: typ.clone(), start: args.start, end: args.end });
363	}
364	Ok(params)
365}
366
367/// Used in several places where the `#[extrinsic_call]` or `#[body]` annotation is missing
368fn missing_call<T>(item_fn: &ItemFn) -> Result<T> {
369	return Err(Error::new(
370		item_fn.block.brace_token.span.join(),
371		"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
372	));
373}
374
375/// Finds the `BenchmarkCallDef` and its index (within the list of stmts for the fn) and
376/// returns them. Also handles parsing errors for invalid / extra call defs. AKA this is
377/// general handling for `#[extrinsic_call]` and `#[block]`
378fn parse_call_def(item_fn: &ItemFn) -> Result<(usize, BenchmarkCallDef)> {
379	// #[extrinsic_call] / #[block] handling
380	let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
381			if let Stmt::Expr(Expr::Call(expr_call), _semi) = child {
382				// #[extrinsic_call] case
383				expr_call.attrs.iter().enumerate().find_map(|(k, attr)| {
384					let segment = attr.path().segments.last()?;
385					let _: keywords::extrinsic_call = syn::parse(segment.ident.to_token_stream().into()).ok()?;
386					let mut expr_call = expr_call.clone();
387
388					// consume #[extrinsic_call] tokens
389					expr_call.attrs.remove(k);
390
391					// extract origin from expr_call
392					let Some(origin) = expr_call.args.first().cloned() else {
393						return Some(Err(Error::new(expr_call.span(), "Single-item extrinsic calls must specify their origin as the first argument.")))
394					};
395
396					Some(Ok((i, BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: attr.span() })))
397				})
398			} else if let Stmt::Expr(Expr::Block(block), _) = child {
399				// #[block] case
400				block.attrs.iter().enumerate().find_map(|(k, attr)| {
401					let segment = attr.path().segments.last()?;
402					let _: keywords::block = syn::parse(segment.ident.to_token_stream().into()).ok()?;
403					let mut block = block.clone();
404
405					// consume #[block] tokens
406					block.attrs.remove(k);
407
408					Some(Ok((i, BenchmarkCallDef::Block { block, attr_span: attr.span() })))
409				})
410			} else {
411				None
412			}
413		}).collect::<Result<Vec<_>>>()?;
414	Ok(match &call_defs[..] {
415		[(i, call_def)] => (*i, call_def.clone()), // = 1
416		[] => return missing_call(item_fn),
417		_ =>
418			return Err(Error::new(
419				call_defs[1].1.attr_span(),
420				"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark.",
421			)),
422	})
423}
424
425impl BenchmarkDef {
426	/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
427	pub fn from(item_fn: &ItemFn) -> Result<BenchmarkDef> {
428		let params = parse_params(item_fn)?;
429		ensure_valid_return_type(item_fn)?;
430		let (i, call_def) = parse_call_def(&item_fn)?;
431
432		let (verify_stmts, last_stmt) = match item_fn.sig.output {
433			ReturnType::Default =>
434			// no return type, last_stmt should be None
435				(Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]), None),
436			ReturnType::Type(_, _) => {
437				// defined return type, last_stmt should be Result<(), BenchmarkError>
438				// compatible and should not be included in verify_stmts
439				if i + 1 >= item_fn.block.stmts.len() {
440					return Err(Error::new(
441						item_fn.block.span(),
442						"Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the \
443						last statement of your benchmark function definition if you have \
444						defined a return type. You should return something compatible \
445						with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement \
446						or change your signature to a blank return type.",
447					))
448				}
449				let Some(stmt) = item_fn.block.stmts.last() else { return missing_call(item_fn) };
450				(
451					Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len() - 1]),
452					Some(stmt.clone()),
453				)
454			},
455		};
456
457		let setup_stmts = Vec::from(&item_fn.block.stmts[0..i]);
458		ensure_no_forbidden_variable_names(&setup_stmts)?;
459
460		Ok(BenchmarkDef {
461			params,
462			setup_stmts,
463			call_def,
464			verify_stmts,
465			last_stmt,
466			fn_sig: item_fn.sig.clone(),
467			fn_vis: item_fn.vis.clone(),
468			fn_attrs: item_fn.attrs.clone(),
469		})
470	}
471}
472
473/// Parses and expands a `#[benchmarks]` or `#[instance_benchmarks]` invocation
474pub fn benchmarks(
475	attrs: TokenStream,
476	tokens: TokenStream,
477	instance: bool,
478) -> syn::Result<TokenStream> {
479	let krate = generate_access_from_frame_or_crate("frame-benchmarking")?;
480	// gather module info
481	let module: ItemMod = syn::parse(tokens)?;
482	let mod_span = module.span();
483	let where_clause = match syn::parse::<Nothing>(attrs.clone()) {
484		Ok(_) =>
485			if instance {
486				quote!(T: Config<I>, I: 'static)
487			} else {
488				quote!(T: Config)
489			},
490		Err(_) => {
491			let mut where_clause_predicates = syn::parse::<WhereClause>(attrs)?.predicates;
492
493			// Ensure the where clause contains the Config trait bound
494			if instance {
495				where_clause_predicates.push(syn::parse_str::<WherePredicate>("T: Config<I>")?);
496				where_clause_predicates.push(syn::parse_str::<WherePredicate>("I:'static")?);
497			} else {
498				where_clause_predicates.push(syn::parse_str::<WherePredicate>("T: Config")?);
499			}
500
501			where_clause_predicates.to_token_stream()
502		},
503	};
504	let mod_vis = module.vis;
505	let mod_name = module.ident;
506
507	// consume #[benchmarks] attribute by excluding it from mod_attrs
508	let mod_attrs: Vec<&Attribute> = module
509		.attrs
510		.iter()
511		.filter(|attr| !attr.path().is_ident(keywords::BENCHMARKS_TOKEN))
512		.collect();
513
514	let mut benchmark_names: Vec<Ident> = Vec::new();
515	let mut extra_benchmark_names: Vec<Ident> = Vec::new();
516	let mut skip_meta_benchmark_names: Vec<Ident> = Vec::new();
517	// Map benchmarks to PoV modes.
518	let mut pov_modes = Vec::new();
519
520	let (_brace, mut content) =
521		module.content.ok_or(syn::Error::new(mod_span, "Module cannot be empty!"))?;
522
523	// find all function defs marked with #[benchmark]
524	let benchmark_fn_metas = content.iter_mut().filter_map(|stmt| {
525		// parse as a function def first
526		let Item::Fn(func) = stmt else { return None };
527
528		// find #[benchmark] attribute on function def
529		let benchmark_attr =
530			func.attrs.iter().find(|attr| attr.path().is_ident(keywords::BENCHMARK_TOKEN))?;
531
532		Some((benchmark_attr.clone(), func.clone(), stmt))
533	});
534
535	// parse individual benchmark defs and args
536	for (benchmark_attr, func, stmt) in benchmark_fn_metas {
537		// parse benchmark def
538		let benchmark_def = BenchmarkDef::from(&func)?;
539
540		// record benchmark name
541		let name = &func.sig.ident;
542		benchmark_names.push(name.clone());
543
544		// Check if we need to parse any args
545		if benchmark_attr.meta.require_path_only().is_err() {
546			// parse any args provided to #[benchmark]
547			let benchmark_attrs: BenchmarkAttrs = benchmark_attr.parse_args()?;
548
549			// record name sets
550			if benchmark_attrs.extra {
551				extra_benchmark_names.push(name.clone());
552			} else if benchmark_attrs.skip_meta {
553				skip_meta_benchmark_names.push(name.clone());
554			}
555
556			if let Some(mode) = benchmark_attrs.pov_mode {
557				let mut modes = Vec::new();
558				// We cannot expand strings here since it is no-std, but syn does not expand bytes.
559				let name = name.to_string();
560				let m = mode.root.to_string();
561				modes.push(quote!(("ALL".as_bytes().to_vec(), #m.as_bytes().to_vec())));
562
563				for attr in mode.per_key.iter() {
564					// syn always puts spaces in quoted paths:
565					let key = attr.key.clone().into_token_stream().to_string().replace(" ", "");
566					let mode = attr.mode.to_string();
567					modes.push(quote!((#key.as_bytes().to_vec(), #mode.as_bytes().to_vec())));
568				}
569
570				pov_modes.push(
571					quote!((#name.as_bytes().to_vec(), #krate::__private::vec![#(#modes),*])),
572				);
573			}
574		}
575
576		// expand benchmark
577		let expanded = expand_benchmark(benchmark_def, name, instance, where_clause.clone());
578
579		// replace original function def with expanded code
580		*stmt = Item::Verbatim(expanded);
581	}
582
583	// generics
584	let type_use_generics = match instance {
585		false => quote!(T),
586		true => quote!(T, I),
587	};
588
589	let frame_system = generate_access_from_frame_or_crate("frame-system")?;
590
591	// benchmark name variables
592	let benchmark_names_str: Vec<String> = benchmark_names.iter().map(|n| n.to_string()).collect();
593	let extra_benchmark_names_str: Vec<String> =
594		extra_benchmark_names.iter().map(|n| n.to_string()).collect();
595	let skip_meta_benchmark_names_str: Vec<String> =
596		skip_meta_benchmark_names.iter().map(|n| n.to_string()).collect();
597	let mut selected_benchmark_mappings: Vec<TokenStream2> = Vec::new();
598	let mut benchmarks_by_name_mappings: Vec<TokenStream2> = Vec::new();
599	let test_idents: Vec<Ident> = benchmark_names_str
600		.iter()
601		.map(|n| Ident::new(format!("test_benchmark_{}", n).as_str(), Span::call_site()))
602		.collect();
603	for i in 0..benchmark_names.len() {
604		let name_ident = &benchmark_names[i];
605		let name_str = &benchmark_names_str[i];
606		let test_ident = &test_idents[i];
607		selected_benchmark_mappings.push(quote!(#name_str => SelectedBenchmark::#name_ident));
608		benchmarks_by_name_mappings.push(quote!(#name_str => Self::#test_ident()))
609	}
610
611	let impl_test_function = content
612		.iter_mut()
613		.find_map(|item| {
614			let Item::Macro(item_macro) = item else {
615				return None;
616			};
617
618			if !item_macro
619				.mac
620				.path
621				.segments
622				.iter()
623				.any(|s| s.ident == "impl_benchmark_test_suite")
624			{
625				return None;
626			}
627
628			let tokens = item_macro.mac.tokens.clone();
629			*item = Item::Verbatim(quote! {});
630
631			Some(quote! {
632				impl_test_function!(
633					(#( {} #benchmark_names )*)
634					(#( #extra_benchmark_names )*)
635					(#( #skip_meta_benchmark_names )*)
636					#tokens
637				);
638			})
639		})
640		.unwrap_or(quote! {});
641
642	// emit final quoted tokens
643	let res = quote! {
644		#(#mod_attrs)
645		*
646		#mod_vis mod #mod_name {
647			#(#content)
648			*
649
650			#[allow(non_camel_case_types)]
651			enum SelectedBenchmark {
652				#(#benchmark_names),
653				*
654			}
655
656			impl<#type_use_generics> #krate::BenchmarkingSetup<#type_use_generics> for SelectedBenchmark where #where_clause {
657				fn components(&self) -> #krate::__private::Vec<(#krate::BenchmarkParameter, u32, u32)> {
658					match self {
659						#(
660							Self::#benchmark_names => {
661								<#benchmark_names as #krate::BenchmarkingSetup<#type_use_generics>>::components(&#benchmark_names)
662							}
663						)
664						*
665					}
666				}
667
668				fn instance(
669					&self,
670					recording: &mut impl #krate::Recording,
671					components: &[(#krate::BenchmarkParameter, u32)],
672					verify: bool,
673				) -> Result<(), #krate::BenchmarkError> {
674					match self {
675						#(
676							Self::#benchmark_names => {
677								<#benchmark_names as #krate::BenchmarkingSetup<
678									#type_use_generics
679								>>::instance(&#benchmark_names, recording, components, verify)
680							}
681						)
682						*
683					}
684				}
685			}
686			#[cfg(any(feature = "runtime-benchmarks", test))]
687			impl<#type_use_generics> #krate::Benchmarking for Pallet<#type_use_generics>
688			where T: #frame_system::Config,#where_clause
689			{
690				fn benchmarks(
691					extra: bool,
692				) -> #krate::__private::Vec<#krate::BenchmarkMetadata> {
693					let mut all_names = #krate::__private::vec![
694						#(#benchmark_names_str),
695						*
696					];
697					if !extra {
698						let extra = [
699							#(#extra_benchmark_names_str),
700							*
701						];
702						all_names.retain(|x| !extra.contains(x));
703					}
704					let pov_modes:
705						#krate::__private::Vec<(
706							#krate::__private::Vec<u8>,
707							#krate::__private::Vec<(
708								#krate::__private::Vec<u8>,
709								#krate::__private::Vec<u8>
710							)>,
711						)> = #krate::__private::vec![
712						#( #pov_modes ),*
713					];
714					all_names.into_iter().map(|benchmark| {
715						let selected_benchmark = match benchmark {
716							#(#selected_benchmark_mappings),
717							*,
718							_ => panic!("all benchmarks should be selectable")
719						};
720						let components = <SelectedBenchmark as #krate::BenchmarkingSetup<#type_use_generics>>::components(&selected_benchmark);
721						let name = benchmark.as_bytes().to_vec();
722						let modes = pov_modes.iter().find(|p| p.0 == name).map(|p| p.1.clone());
723
724						#krate::BenchmarkMetadata {
725							name: benchmark.as_bytes().to_vec(),
726							components,
727							pov_modes: modes.unwrap_or_default(),
728						}
729					}).collect::<#krate::__private::Vec<_>>()
730				}
731
732				fn run_benchmark(
733					extrinsic: &[u8],
734					c: &[(#krate::BenchmarkParameter, u32)],
735					whitelist: &[#krate::__private::TrackedStorageKey],
736					verify: bool,
737					internal_repeats: u32,
738				) -> Result<#krate::__private::Vec<#krate::BenchmarkResult>, #krate::BenchmarkError> {
739					#krate::benchmarking::wipe_db();
740					let extrinsic = #krate::__private::str::from_utf8(extrinsic).map_err(|_| "`extrinsic` is not a valid utf-8 string!")?;
741					let selected_benchmark = match extrinsic {
742						#(#selected_benchmark_mappings),
743						*,
744						_ => return Err("Could not find extrinsic.".into()),
745					};
746					let mut whitelist = whitelist.to_vec();
747					let whitelisted_caller_key = <#frame_system::Account<
748						T,
749					> as #krate::__private::storage::StorageMap<_, _,>>::hashed_key_for(
750						#krate::whitelisted_caller::<T::AccountId>()
751					);
752					whitelist.push(whitelisted_caller_key.into());
753					let transactional_layer_key = #krate::__private::TrackedStorageKey::new(
754						#krate::__private::storage::transactional::TRANSACTION_LEVEL_KEY.into(),
755					);
756					whitelist.push(transactional_layer_key);
757					// Whitelist the `:extrinsic_index`.
758					let extrinsic_index = #krate::__private::TrackedStorageKey::new(
759						#krate::__private::well_known_keys::EXTRINSIC_INDEX.into()
760					);
761					whitelist.push(extrinsic_index);
762					// Whitelist the `:intrablock_entropy`.
763					let intrablock_entropy = #krate::__private::TrackedStorageKey::new(
764						#krate::__private::well_known_keys::INTRABLOCK_ENTROPY.into()
765					);
766					whitelist.push(intrablock_entropy);
767
768					#krate::benchmarking::set_whitelist(whitelist.clone());
769					let mut results: #krate::__private::Vec<#krate::BenchmarkResult> = #krate::__private::Vec::new();
770
771					let on_before_start = || {
772						// Set the block number to at least 1 so events are deposited.
773						if #krate::__private::Zero::is_zero(&#frame_system::Pallet::<T>::block_number()) {
774							#frame_system::Pallet::<T>::set_block_number(1u32.into());
775						}
776
777						// Commit the externalities to the database, flushing the DB cache.
778						// This will enable worst case scenario for reading from the database.
779						#krate::benchmarking::commit_db();
780
781						// Access all whitelisted keys to get them into the proof recorder since the
782						// recorder does now have a whitelist.
783						for key in &whitelist {
784							#krate::__private::storage::unhashed::get_raw(&key.key);
785						}
786
787						// Reset the read/write counter so we don't count operations in the setup process.
788						#krate::benchmarking::reset_read_write_count();
789					};
790
791					// Always do at least one internal repeat...
792					for _ in 0 .. internal_repeats.max(1) {
793						// Always reset the state after the benchmark.
794						#krate::__private::defer!(#krate::benchmarking::wipe_db());
795
796						// Time the extrinsic logic.
797						#krate::__private::log::trace!(
798							target: "benchmark",
799							"Start Benchmark: {} ({:?})",
800							extrinsic,
801							c
802						);
803
804						let mut recording = #krate::BenchmarkRecording::new(&on_before_start);
805						<SelectedBenchmark as #krate::BenchmarkingSetup<#type_use_generics>>::instance(&selected_benchmark, &mut recording, c, verify)?;
806
807						// Calculate the diff caused by the benchmark.
808						let elapsed_extrinsic = recording.elapsed_extrinsic().expect("elapsed time should be recorded");
809						let diff_pov = recording.diff_pov().unwrap_or_default();
810
811						// Commit the changes to get proper write count
812						#krate::benchmarking::commit_db();
813						#krate::__private::log::trace!(
814							target: "benchmark",
815							"End Benchmark: {} ns", elapsed_extrinsic
816						);
817						let read_write_count = #krate::benchmarking::read_write_count();
818						#krate::__private::log::trace!(
819							target: "benchmark",
820							"Read/Write Count {:?}", read_write_count
821						);
822
823						// Time the storage root recalculation.
824						let start_storage_root = #krate::current_time();
825						#krate::__private::storage_root(#krate::__private::StateVersion::V1);
826						let finish_storage_root = #krate::current_time();
827						let elapsed_storage_root = finish_storage_root - start_storage_root;
828
829						let skip_meta = [ #(#skip_meta_benchmark_names_str),* ];
830						let read_and_written_keys = if skip_meta.contains(&extrinsic) {
831							#krate::__private::vec![(b"Skipped Metadata".to_vec(), 0, 0, false)]
832						} else {
833							#krate::benchmarking::get_read_and_written_keys()
834						};
835
836						results.push(#krate::BenchmarkResult {
837							components: c.to_vec(),
838							extrinsic_time: elapsed_extrinsic,
839							storage_root_time: elapsed_storage_root,
840							reads: read_write_count.0,
841							repeat_reads: read_write_count.1,
842							writes: read_write_count.2,
843							repeat_writes: read_write_count.3,
844							proof_size: diff_pov,
845							keys: read_and_written_keys,
846						});
847					}
848
849					return Ok(results);
850				}
851			}
852
853			#[cfg(test)]
854			impl<#type_use_generics> Pallet<#type_use_generics> where T: #frame_system::Config, #where_clause {
855				/// Test a particular benchmark by name.
856				///
857				/// This isn't called `test_benchmark_by_name` just in case some end-user eventually
858				/// writes a benchmark, itself called `by_name`; the function would be shadowed in
859				/// that case.
860				///
861				/// This is generally intended to be used by child test modules such as those created
862				/// by the `impl_benchmark_test_suite` macro. However, it is not an error if a pallet
863				/// author chooses not to implement benchmarks.
864				#[allow(unused)]
865				fn test_bench_by_name(name: &[u8]) -> Result<(), #krate::BenchmarkError> {
866					let name = #krate::__private::str::from_utf8(name)
867						.map_err(|_| -> #krate::BenchmarkError { "`name` is not a valid utf8 string!".into() })?;
868					match name {
869						#(#benchmarks_by_name_mappings),
870						*,
871						_ => Err("Could not find test for requested benchmark.".into()),
872					}
873				}
874			}
875
876			#impl_test_function
877		}
878		#mod_vis use #mod_name::*;
879	};
880	Ok(res.into())
881}
882
883/// Prepares a [`Vec<ParamDef>`] to be interpolated by [`quote!`] by creating easily-iterable
884/// arrays formatted in such a way that they can be interpolated directly.
885struct UnrolledParams {
886	param_ranges: Vec<TokenStream2>,
887	param_names: Vec<TokenStream2>,
888}
889
890impl UnrolledParams {
891	/// Constructs an [`UnrolledParams`] from a [`Vec<ParamDef>`]
892	fn from(params: &Vec<ParamDef>) -> UnrolledParams {
893		let param_ranges: Vec<TokenStream2> = params
894			.iter()
895			.map(|p| {
896				let name = Ident::new(&p.name, Span::call_site());
897				let start = &p.start;
898				let end = &p.end;
899				quote!(#name, #start, #end)
900			})
901			.collect();
902		let param_names: Vec<TokenStream2> = params
903			.iter()
904			.map(|p| {
905				let name = Ident::new(&p.name, Span::call_site());
906				quote!(#name)
907			})
908			.collect();
909		UnrolledParams { param_ranges, param_names }
910	}
911}
912
913/// Performs expansion of an already-parsed [`BenchmarkDef`].
914fn expand_benchmark(
915	benchmark_def: BenchmarkDef,
916	name: &Ident,
917	is_instance: bool,
918	where_clause: TokenStream2,
919) -> TokenStream2 {
920	// set up variables needed during quoting
921	let krate = match generate_access_from_frame_or_crate("frame-benchmarking") {
922		Ok(ident) => ident,
923		Err(err) => return err.to_compile_error().into(),
924	};
925	let frame_system = match generate_access_from_frame_or_crate("frame-system") {
926		Ok(path) => path,
927		Err(err) => return err.to_compile_error().into(),
928	};
929	let codec = quote!(#krate::__private::codec);
930	let traits = quote!(#krate::__private::traits);
931	let setup_stmts = benchmark_def.setup_stmts;
932	let verify_stmts = benchmark_def.verify_stmts;
933	let last_stmt = benchmark_def.last_stmt;
934	let test_ident =
935		Ident::new(format!("test_benchmark_{}", name.to_string()).as_str(), Span::call_site());
936
937	// unroll params (prepare for quoting)
938	let unrolled = UnrolledParams::from(&benchmark_def.params);
939	let param_names = unrolled.param_names;
940	let param_ranges = unrolled.param_ranges;
941
942	let type_use_generics = match is_instance {
943		false => quote!(T),
944		true => quote!(T, I),
945	};
946
947	// used in the benchmarking impls
948	let (pre_call, post_call, fn_call_body) = match &benchmark_def.call_def {
949		BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => {
950			let mut expr_call = expr_call.clone();
951
952			// remove first arg from expr_call
953			let mut final_args = Punctuated::<Expr, Comma>::new();
954			let args: Vec<&Expr> = expr_call.args.iter().collect();
955			for arg in &args[1..] {
956				final_args.push((*(*arg)).clone());
957			}
958			expr_call.args = final_args;
959
960			let origin = match origin {
961				Expr::Cast(t) => {
962					let ty = t.ty.clone();
963					quote_spanned! { origin.span() =>
964						<<T as #frame_system::Config>::RuntimeOrigin as From<#ty>>::from(#origin);
965					}
966				},
967				_ => quote_spanned! { origin.span() =>
968					Into::<<T as #frame_system::Config>::RuntimeOrigin>::into(#origin);
969				},
970			};
971
972			// determine call name (handles `_` and normal call syntax)
973			let expr_span = expr_call.span();
974			let call_err = || {
975				syn::Error::new(expr_span, "Extrinsic call must be a function call or `_`")
976					.to_compile_error()
977			};
978			let call_name = match *expr_call.func {
979				Expr::Path(expr_path) => {
980					// normal function call
981					let Some(segment) = expr_path.path.segments.last() else { return call_err() };
982					segment.ident.to_string()
983				},
984				Expr::Infer(_) => {
985					// `_` style
986					// replace `_` with fn name
987					name.to_string()
988				},
989				_ => return call_err(),
990			};
991
992			// modify extrinsic call to be prefixed with "new_call_variant"
993			let call_name = format!("new_call_variant_{}", call_name);
994			let mut punct: Punctuated<PathSegment, PathSep> = Punctuated::new();
995			punct.push(PathSegment {
996				arguments: PathArguments::None,
997				ident: Ident::new(call_name.as_str(), Span::call_site()),
998			});
999			*expr_call.func = Expr::Path(ExprPath {
1000				attrs: vec![],
1001				qself: None,
1002				path: Path { leading_colon: None, segments: punct },
1003			});
1004			let pre_call = quote! {
1005				let __call = Call::<#type_use_generics>::#expr_call;
1006				let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
1007			};
1008			let post_call = quote! {
1009				let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
1010					::decode(&mut &__benchmarked_call_encoded[..])
1011					.expect("call is encoded above, encoding must be correct");
1012				#[allow(clippy::useless_conversion)]
1013				let __origin = #origin;
1014				<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
1015					__call_decoded,
1016					__origin,
1017				)
1018			};
1019			(
1020				// (pre_call, post_call, fn_call_body):
1021				pre_call.clone(),
1022				quote!(#post_call?;),
1023				quote! {
1024					#pre_call
1025					#post_call.unwrap();
1026				},
1027			)
1028		},
1029		BenchmarkCallDef::Block { block, attr_span: _ } =>
1030			(quote!(), quote!(#block), quote!(#block)),
1031	};
1032
1033	let vis = benchmark_def.fn_vis;
1034
1035	// remove #[benchmark] attribute
1036	let fn_attrs = benchmark_def
1037		.fn_attrs
1038		.iter()
1039		.filter(|attr| !attr.path().is_ident(keywords::BENCHMARK_TOKEN));
1040
1041	// modify signature generics, ident, and inputs, e.g:
1042	// before: `fn bench(u: Linear<1, 100>) -> Result<(), BenchmarkError>`
1043	// after: `fn _bench <T, I>(u: u32, verify: bool) where T: Config<I>, I: 'static -> Result<(),
1044	// BenchmarkError>`
1045	let mut sig = benchmark_def.fn_sig;
1046	sig.generics = parse_quote!(<#type_use_generics>);
1047	sig.generics.where_clause = parse_quote!(where #where_clause);
1048	sig.ident =
1049		Ident::new(format!("_{}", name.to_token_stream().to_string()).as_str(), Span::call_site());
1050	let mut fn_param_inputs: Vec<TokenStream2> =
1051		param_names.iter().map(|name| quote!(#name: u32)).collect();
1052	fn_param_inputs.push(quote!(verify: bool));
1053	sig.inputs = parse_quote!(#(#fn_param_inputs),*);
1054
1055	// used in instance() impl
1056	let impl_last_stmt = match &last_stmt {
1057		Some(stmt) => quote!(#stmt),
1058		None => quote!(Ok(())),
1059	};
1060	let fn_attrs_clone = fn_attrs.clone();
1061
1062	let fn_def = quote! {
1063		#(
1064			#fn_attrs_clone
1065		)*
1066		#vis #sig {
1067			#(
1068				#setup_stmts
1069			)*
1070			#fn_call_body
1071			if verify {
1072				#(
1073					#verify_stmts
1074				)*
1075			}
1076			#last_stmt
1077		}
1078	};
1079
1080	// generate final quoted tokens
1081	let res = quote! {
1082		// benchmark function definition
1083		#fn_def
1084
1085		#[allow(non_camel_case_types)]
1086		#(
1087			#fn_attrs
1088		)*
1089		struct #name;
1090
1091		#[allow(unused_variables)]
1092		impl<#type_use_generics> #krate::BenchmarkingSetup<#type_use_generics>
1093		for #name where #where_clause {
1094			fn components(&self) -> #krate::__private::Vec<(#krate::BenchmarkParameter, u32, u32)> {
1095				#krate::__private::vec! [
1096					#(
1097						(#krate::BenchmarkParameter::#param_ranges)
1098					),*
1099				]
1100			}
1101
1102			fn instance(
1103				&self,
1104				recording: &mut impl #krate::Recording,
1105				components: &[(#krate::BenchmarkParameter, u32)],
1106				verify: bool
1107			) -> Result<(), #krate::BenchmarkError> {
1108				#(
1109					// prepare instance #param_names
1110					let #param_names = components.iter()
1111						.find(|&c| c.0 == #krate::BenchmarkParameter::#param_names)
1112						.ok_or("Could not find component during benchmark preparation.")?
1113						.1;
1114				)*
1115
1116				// benchmark setup code
1117				#(
1118					#setup_stmts
1119				)*
1120				#pre_call
1121				recording.start();
1122				#post_call
1123				recording.stop();
1124				if verify {
1125					#(
1126						#verify_stmts
1127					)*
1128				}
1129				#impl_last_stmt
1130			}
1131		}
1132
1133		#[cfg(test)]
1134		impl<#type_use_generics> Pallet<#type_use_generics> where T: #frame_system::Config, #where_clause {
1135			#[allow(unused)]
1136			fn #test_ident() -> Result<(), #krate::BenchmarkError> {
1137				let selected_benchmark = SelectedBenchmark::#name;
1138				let components = <
1139					SelectedBenchmark as #krate::BenchmarkingSetup<T, _>
1140				>::components(&selected_benchmark);
1141				let execute_benchmark = |
1142					c: #krate::__private::Vec<(#krate::BenchmarkParameter, u32)>
1143				| -> Result<(), #krate::BenchmarkError> {
1144					// Always reset the state after the benchmark.
1145					#krate::__private::defer!(#krate::benchmarking::wipe_db());
1146
1147					let on_before_start = || {
1148						// Set the block number to at least 1 so events are deposited.
1149						if #krate::__private::Zero::is_zero(&#frame_system::Pallet::<T>::block_number()) {
1150							#frame_system::Pallet::<T>::set_block_number(1u32.into());
1151						}
1152					};
1153
1154					// Run execution + verification
1155					<SelectedBenchmark as #krate::BenchmarkingSetup<T, _>>::test_instance(&selected_benchmark,  &c, &on_before_start)
1156				};
1157
1158				if components.is_empty() {
1159					execute_benchmark(Default::default())?;
1160				} else {
1161					let num_values: u32 = if let Ok(ev) = std::env::var("VALUES_PER_COMPONENT") {
1162						ev.parse().map_err(|_| {
1163							#krate::BenchmarkError::Stop(
1164								"Could not parse env var `VALUES_PER_COMPONENT` as u32."
1165							)
1166						})?
1167					} else {
1168						6
1169					};
1170
1171					if num_values < 2 {
1172						return Err("`VALUES_PER_COMPONENT` must be at least 2".into());
1173					}
1174
1175					for (name, low, high) in components.clone().into_iter() {
1176						// Test the lowest, highest (if its different from the lowest)
1177						// and up to num_values-2 more equidistant values in between.
1178						// For 0..10 and num_values=6 this would mean: [0, 2, 4, 6, 8, 10]
1179						if high < low {
1180							return Err("The start of a `ParamRange` must be less than or equal to the end".into());
1181						}
1182
1183						let mut values = #krate::__private::vec![low];
1184						let diff = (high - low).min(num_values - 1);
1185						let slope = (high - low) as f32 / diff as f32;
1186
1187						for i in 1..=diff {
1188							let value = ((low as f32 + slope * i as f32) as u32)
1189											.clamp(low, high);
1190							values.push(value);
1191						}
1192
1193						for component_value in values {
1194							// Select the max value for all the other components.
1195							let c: #krate::__private::Vec<(#krate::BenchmarkParameter, u32)> = components
1196								.iter()
1197								.map(|(n, _, h)|
1198									if *n == name {
1199										(*n, component_value)
1200									} else {
1201										(*n, *h)
1202									}
1203								)
1204								.collect();
1205
1206							execute_benchmark(c)?;
1207						}
1208					}
1209				}
1210				return Ok(());
1211			}
1212		}
1213	};
1214	res
1215}