diff --git a/substrate/frame/balances/src/benchmarking.rs b/substrate/frame/balances/src/benchmarking.rs
index c2ed0ace54100a08771b510aadb793c2cd5de682..b36fe1e341de32cfab450b6a91fd0a19cd5ce7b8 100644
--- a/substrate/frame/balances/src/benchmarking.rs
+++ b/substrate/frame/balances/src/benchmarking.rs
@@ -225,7 +225,7 @@ mod benchmarks {
 	}
 
 	#[benchmark]
-	fn force_unreserve() {
+	fn force_unreserve() -> Result<(), BenchmarkError> {
 		let user: T::AccountId = account("user", 0, SEED);
 		let user_lookup = T::Lookup::unlookup(user.clone());
 
@@ -244,6 +244,8 @@ mod benchmarks {
 
 		assert!(Balances::<T, I>::reserved_balance(&user).is_zero());
 		assert_eq!(Balances::<T, I>::free_balance(&user), balance);
+
+		Ok(())
 	}
 
 	impl_benchmark_test_suite! {
diff --git a/substrate/frame/benchmarking/src/lib.rs b/substrate/frame/benchmarking/src/lib.rs
index f5dc513650e217ed859e9b794133b8b98964c89b..7110c378d581eec3733fb0acf2d6abb8a13b22db 100644
--- a/substrate/frame/benchmarking/src/lib.rs
+++ b/substrate/frame/benchmarking/src/lib.rs
@@ -114,21 +114,22 @@ pub use v1::*;
 /// Within a `#[benchmarks]` or `#[instance_benchmarks]` module, you can define individual
 /// benchmarks using the `#[benchmark]` attribute, as shown in the example above.
 ///
-/// The `#[benchmark]` attribute expects a function definition with a blank return type and
-/// zero or more arguments whose names are valid
-/// [BenchmarkParameter](`crate::BenchmarkParameter`) parameters, such as `x`, `y`, `a`, `b`,
-/// etc., and whose param types must implement [ParamRange](`v2::ParamRange`). At the moment
-/// the only valid type that implements [ParamRange](`v2::ParamRange`) is
-/// [Linear](`v2::Linear`).
-///
-/// The valid syntax for defining a [Linear](`v2::Linear`)is `Linear<A, B>` where `A`, and `B`
+/// The `#[benchmark]` attribute expects a function definition with a blank return type (or a
+/// return type compatible with `Result<(), BenchmarkError>`, as discussed below) and zero or
+/// more arguments whose names are valid [BenchmarkParameter](`crate::BenchmarkParameter`)
+/// parameters, such as `x`, `y`, `a`, `b`, etc., and whose param types must implement
+/// [ParamRange](`v2::ParamRange`). At the moment the only valid type that implements
+/// [ParamRange](`v2::ParamRange`) is [Linear](`v2::Linear`).
+///
+/// The valid syntax for defining a [Linear](`v2::Linear`) is `Linear<A, B>` where `A`, and `B`
 /// are valid integer literals (that fit in a `u32`), such that `B` >= `A`.
 ///
-/// Note that the benchmark function definition does not actually expand as a function
-/// definition, but rather is used to automatically create a number of impls and structs
-/// required by the benchmarking engine. For this reason, the visibility of the function
-/// definition as well as the return type are not used for any purpose and are discarded by the
-/// expansion code.
+/// Anywhere within a benchmark function you may use the generic `T: Config` parameter as well
+/// as `I` in the case of an `#[instance_benchmarks]` module. You should not add these to the
+/// function signature as this will be handled automatically for you based on whether this is a
+/// `#[benchmarks]` or `#[instance_benchmarks]` module and whatever [where clause](#where-clause)
+/// you have defined for the the module. You should not manually add any generics to the
+/// signature of your benchmark function.
 ///
 /// Also note that the `// setup code` and `// verification code` comments shown above are not
 /// required and are included simply for demonstration purposes.
@@ -189,10 +190,10 @@ pub use v1::*;
 ///
 /// #### `skip_meta`
 ///
-/// Specifies that the benchmarking framework should not analyze the storage keys that
+/// Specifies that the benchmarking framework should not analyze the storage keys that the
 /// benchmarked code read or wrote. This useful to suppress the prints in the form of unknown
-/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis
-/// of all accesses, not just ones without metadata.
+/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis of
+/// all accesses, not just ones without metadata.
 ///
 /// ## Where Clause
 ///
@@ -231,6 +232,79 @@ pub use v1::*;
 /// 	);
 /// }
 /// ```
+///
+/// ## Benchmark Function Generation
+///
+/// The benchmark function definition that you provide is used to automatically create a number
+/// of impls and structs required by the benchmarking engine. Additionally, a benchmark
+/// function is also generated that resembles the function definition you provide, with a few
+/// modifications:
+/// 1. The function name is transformed from i.e. `original_name` to `_original_name` so as not
+///    to collide with the struct `original_name` that is created for some of the benchmarking
+///    engine impls.
+/// 2. Appropriate `T: Config` and `I` (if this is an instance benchmark) generics are added to
+///    the function automatically during expansion, so you should not add these manually on
+///    your function definition (but you may make use of `T` and `I` anywhere within your
+///    benchmark function, in any of the three sections (setup, call, verification).
+/// 3. Arguments such as `u: Linear<10, 100>` are converted to `u: u32` to make the function
+///    directly callable.
+/// 4. A `verify: bool` param is added as the last argument. Specifying `true` will result in
+///    the verification section of your function executing, while a value of `false` will skip
+///    verification.
+/// 5. If you specify a return type on the function definition, it must conform to the [rules
+///    below](#support-for-result-benchmarkerror-and-the--operator), and the last statement of
+///    the function definition must resolve to something compatible with `Result<(),
+///    BenchmarkError>`.
+///
+/// The reason we generate an actual function as part of the expansion is to allow the compiler
+/// to enforce several constraints that would otherwise be difficult to enforce and to reduce
+/// developer confusion (especially regarding the use of the `?` operator, as covered below).
+///
+/// Note that any attributes, comments, and doc comments attached to your benchmark function
+/// definition are also carried over onto the resulting benchmark function and the struct for
+/// that benchmark. As a result you should be careful about what attributes you attach here as
+/// they will be replicated in multiple places.
+///
+/// ### Support for `Result<(), BenchmarkError>` and the `?` operator
+///
+/// You may optionally specify `Result<(), BenchmarkError>` as the return type of your
+/// benchmark function definition. If you do so, you must return a compatible `Result<(),
+/// BenchmarkError>` as the *last statement* of your benchmark function definition. You may
+/// also use the `?` operator throughout your benchmark function definition if you choose to
+/// follow this route. See the example below:
+///
+/// ```ignore
+/// #![cfg(feature = "runtime-benchmarks")]
+///
+/// use super::{mock_helpers::*, Pallet as MyPallet};
+/// use frame_benchmarking::v2::*;
+///
+/// #[benchmarks]
+/// mod benchmarks {
+/// 	use super::*;
+///
+/// 	#[benchmark]
+/// 	fn bench_name(x: Linear<5, 25>) -> Result<(), BenchmarkError> {
+/// 		// setup code
+/// 		let z = x + 4;
+/// 		let caller = whitelisted_caller();
+///
+/// 		// note we can make use of the ? operator here because of the return type
+/// 		something(z)?;
+///
+/// 		#[extrinsic_call]
+/// 		extrinsic_name(SystemOrigin::Signed(caller), other, arguments);
+///
+/// 		// verification code
+/// 		assert_eq!(MyPallet::<T>::my_var(), z);
+///
+/// 		// we must return a valid `Result<(), BenchmarkError>` as the last line of our benchmark
+/// 		// function definition. This line is not included as part of the verification code that
+/// 		// appears above it.
+/// 		Ok(())
+/// 	}
+/// }
+/// ```
 pub mod v2 {
 	pub use super::*;
 	pub use frame_support_procedural::{
@@ -240,7 +314,7 @@ pub mod v2 {
 	// Used in #[benchmark] implementation to ensure that benchmark function arguments
 	// implement [`ParamRange`].
 	#[doc(hidden)]
-	pub use static_assertions::assert_impl_all;
+	pub use static_assertions::{assert_impl_all, assert_type_eq_all};
 
 	/// Used by the new benchmarking code to specify that a benchmarking variable is linear
 	/// over some specified range, i.e. `Linear<0, 1_000>` means that the corresponding variable
diff --git a/substrate/frame/examples/basic/src/benchmarking.rs b/substrate/frame/examples/basic/src/benchmarking.rs
index 0d2e9c5b3b1b37de1cd5689f83fc7e439c77b3ec..4b2ebb41fbda1d6d8a0d12a65b32391510f1a488 100644
--- a/substrate/frame/examples/basic/src/benchmarking.rs
+++ b/substrate/frame/examples/basic/src/benchmarking.rs
@@ -21,11 +21,7 @@
 #![cfg(feature = "runtime-benchmarks")]
 
 use crate::*;
-use frame_benchmarking::v1::{
-	impl_benchmark_test_suite,
-	v2::{benchmarks, Linear},
-	whitelisted_caller,
-};
+use frame_benchmarking::v2::*;
 use frame_system::RawOrigin;
 
 // To actually run this benchmark on pallet-example-basic, we need to put this pallet into the
@@ -55,19 +51,31 @@ mod benchmarks {
 		assert_eq!(Pallet::<T>::dummy(), Some(value))
 	}
 
+	// An example method that returns a Result that can be called within a benchmark
+	fn example_result_method() -> Result<(), BenchmarkError> {
+		Ok(())
+	}
+
 	// This will measure the execution time of `accumulate_dummy`.
 	// The benchmark execution phase is shorthanded. When the name of the benchmark case is the same
 	// as the extrinsic call. `_(...)` is used to represent the extrinsic name.
 	// The benchmark verification phase is omitted.
 	#[benchmark]
-	fn accumulate_dummy() {
+	fn accumulate_dummy() -> Result<(), BenchmarkError> {
 		let value = 1000u32.into();
 		// The caller account is whitelisted for DB reads/write by the benchmarking macro.
 		let caller: T::AccountId = whitelisted_caller();
 
+		// an example of calling something result-based within a benchmark using the ? operator
+		// this necessitates specifying the `Result<(), BenchmarkError>` return type
+		example_result_method()?;
+
 		// You can use `_` if the name of the Call matches the benchmark name.
 		#[extrinsic_call]
 		_(RawOrigin::Signed(caller), value);
+
+		// need this to be compatible with the return type
+		Ok(())
 	}
 
 	/// You can write helper functions in here since its a normal Rust module.
diff --git a/substrate/frame/support/procedural/src/benchmark.rs b/substrate/frame/support/procedural/src/benchmark.rs
index 6cf1d7ecc23afc69cee4d5fb5fcd7bca1278c8b6..1aca2f87b1d1d89615d5f1554cbaac872e8a5b14 100644
--- a/substrate/frame/support/procedural/src/benchmark.rs
+++ b/substrate/frame/support/procedural/src/benchmark.rs
@@ -25,11 +25,13 @@ use quote::{quote, quote_spanned, ToTokens};
 use syn::{
 	parenthesized,
 	parse::{Nothing, ParseStream},
+	parse_quote,
 	punctuated::Punctuated,
 	spanned::Spanned,
 	token::{Colon2, Comma, Gt, Lt, Paren},
 	Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, LitInt,
-	Pat, Path, PathArguments, PathSegment, Result, Stmt, Token, Type, WhereClause,
+	Pat, Path, PathArguments, PathSegment, Result, ReturnType, Signature, Stmt, Token, Type,
+	TypePath, Visibility, WhereClause,
 };
 
 mod keywords {
@@ -41,6 +43,8 @@ mod keywords {
 	custom_keyword!(extra);
 	custom_keyword!(extrinsic_call);
 	custom_keyword!(skip_meta);
+	custom_keyword!(BenchmarkError);
+	custom_keyword!(Result);
 }
 
 /// This represents the raw parsed data for a param definition such as `x: Linear<10, 20>`.
@@ -145,62 +149,121 @@ struct BenchmarkDef {
 	setup_stmts: Vec<Stmt>,
 	call_def: BenchmarkCallDef,
 	verify_stmts: Vec<Stmt>,
+	last_stmt: Option<Stmt>,
 	extra: bool,
 	skip_meta: bool,
+	fn_sig: Signature,
+	fn_vis: Visibility,
+	fn_attrs: Vec<Attribute>,
 }
 
-impl BenchmarkDef {
-	/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
-	pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result<BenchmarkDef> {
-		let mut params: Vec<ParamDef> = Vec::new();
+/// used to parse something compatible with `Result<T, E>`
+#[derive(Parse)]
+struct ResultDef {
+	_result_kw: keywords::Result,
+	_lt: Token![<],
+	unit: Type,
+	_comma: Comma,
+	e_type: TypePath,
+	_gt: Token![>],
+}
 
-		// parse params such as "x: Linear<0, 1>"
-		for arg in &item_fn.sig.inputs {
-			let invalid_param = |span| {
-				return Err(Error::new(span, "Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.", ))
+/// Ensures that `ReturnType` is a `Result<(), BenchmarkError>`, if specified
+fn ensure_valid_return_type(item_fn: &ItemFn) -> Result<()> {
+	if let ReturnType::Type(_, typ) = &item_fn.sig.output {
+		let non_unit = |span| return Err(Error::new(span, "expected `()`"));
+		let Type::Path(TypePath { path, qself: _ }) = &**typ else {
+				return Err(Error::new(
+					typ.span(),
+					"Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions",
+				))
 			};
+		let seg = path
+			.segments
+			.last()
+			.expect("to be parsed as a TypePath, it must have at least one segment; qed");
+		let res: ResultDef = syn::parse2(seg.to_token_stream())?;
+		// ensure T in Result<T, E> is ()
+		let Type::Tuple(tup) = res.unit else { return non_unit(res.unit.span()) };
+		if !tup.elems.is_empty() {
+			return non_unit(tup.span())
+		}
+		let TypePath { path, qself: _ } = res.e_type;
+		let seg = path
+			.segments
+			.last()
+			.expect("to be parsed as a TypePath, it must have at least one segment; qed");
+		syn::parse2::<keywords::BenchmarkError>(seg.to_token_stream())?;
+	}
+	Ok(())
+}
 
-			let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
-			let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
+/// Parses params such as `x: Linear<0, 1>`
+fn parse_params(item_fn: &ItemFn) -> Result<Vec<ParamDef>> {
+	let mut params: Vec<ParamDef> = Vec::new();
+	for arg in &item_fn.sig.inputs {
+		let invalid_param = |span| {
+			return Err(Error::new(
+				span,
+				"Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.",
+			))
+		};
 
-			// check param name
-			let var_span = ident.span();
-			let invalid_param_name = || {
-				return Err(Error::new(
+		let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) };
+		let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) };
+
+		// check param name
+		let var_span = ident.span();
+		let invalid_param_name = || {
+			return Err(Error::new(
 					var_span,
 					"Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.",
 				))
-			};
-			let name = ident.ident.to_token_stream().to_string();
-			if name.len() > 1 {
-				return invalid_param_name()
-			};
-			let Some(name_char) = name.chars().next() else { return invalid_param_name() };
-			if !name_char.is_alphabetic() || !name_char.is_lowercase() {
-				return invalid_param_name()
-			}
+		};
+		let name = ident.ident.to_token_stream().to_string();
+		if name.len() > 1 {
+			return invalid_param_name()
+		};
+		let Some(name_char) = name.chars().next() else { return invalid_param_name() };
+		if !name_char.is_alphabetic() || !name_char.is_lowercase() {
+			return invalid_param_name()
+		}
 
-			// parse type
-			let typ = &*arg.ty;
-			let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
-			let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
-			let args = segment.arguments.to_token_stream().into();
-			let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
-			let Ok(start) = args.start.base10_parse::<u32>() else { return invalid_param(args.start.span()) };
-			let Ok(end) = args.end.base10_parse::<u32>() else { return invalid_param(args.end.span()) };
+		// parse type
+		let typ = &*arg.ty;
+		let Type::Path(tpath) = typ else { return invalid_param(typ.span()) };
+		let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) };
+		let args = segment.arguments.to_token_stream().into();
+		let Ok(args) = syn::parse::<RangeArgs>(args) else { return invalid_param(typ.span()) };
+		let Ok(start) = args.start.base10_parse::<u32>() else { return invalid_param(args.start.span()) };
+		let Ok(end) = args.end.base10_parse::<u32>() else { return invalid_param(args.end.span()) };
+
+		if end < start {
+			return Err(Error::new(
+				args.start.span(),
+				"The start of a `ParamRange` must be less than or equal to the end",
+			))
+		}
 
-			if end < start {
-				return Err(Error::new(
-					args.start.span(),
-					"The start of a `ParamRange` must be less than or equal to the end",
-				))
-			}
+		params.push(ParamDef { name, typ: typ.clone(), start, end });
+	}
+	Ok(params)
+}
 
-			params.push(ParamDef { name, typ: typ.clone(), start, end });
-		}
+/// Used in several places where the `#[extrinsic_call]` or `#[body]` annotation is missing
+fn missing_call<T>(item_fn: &ItemFn) -> Result<T> {
+	return Err(Error::new(
+		item_fn.block.brace_token.span,
+		"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
+	))
+}
 
-		// #[extrinsic_call] / #[block] handling
-		let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
+/// Finds the `BenchmarkCallDef` and its index (within the list of stmts for the fn) and
+/// returns them. Also handles parsing errors for invalid / extra call defs. AKA this is
+/// general handling for `#[extrinsic_call]` and `#[block]`
+fn parse_call_def(item_fn: &ItemFn) -> Result<(usize, BenchmarkCallDef)> {
+	// #[extrinsic_call] / #[block] handling
+	let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| {
 			if let Stmt::Semi(Expr::Call(expr_call), _semi) = child {
 				// #[extrinsic_call] case
 				expr_call.attrs.iter().enumerate().find_map(|(k, attr)| {
@@ -234,25 +297,60 @@ impl BenchmarkDef {
 				None
 			}
 		}).collect::<Result<Vec<_>>>()?;
-		let (i, call_def) = match &call_defs[..] {
-			[(i, call_def)] => (*i, call_def.clone()), // = 1
-			[] => return Err(Error::new( // = 0
-				item_fn.block.brace_token.span,
-				"No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body."
-			)),
-			_ => return Err(Error::new( // > 1
+	Ok(match &call_defs[..] {
+		[(i, call_def)] => (*i, call_def.clone()), // = 1
+		[] => return missing_call(item_fn),
+		_ =>
+			return Err(Error::new(
 				call_defs[1].1.attr_span(),
-				"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark."
+				"Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark.",
 			)),
+	})
+}
+
+impl BenchmarkDef {
+	/// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node.
+	pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result<BenchmarkDef> {
+		let params = parse_params(item_fn)?;
+		ensure_valid_return_type(item_fn)?;
+		let (i, call_def) = parse_call_def(&item_fn)?;
+
+		let (verify_stmts, last_stmt) = match item_fn.sig.output {
+			ReturnType::Default =>
+			// no return type, last_stmt should be None
+				(Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]), None),
+			ReturnType::Type(_, _) => {
+				// defined return type, last_stmt should be Result<(), BenchmarkError>
+				// compatible and should not be included in verify_stmts
+				if i + 1 >= item_fn.block.stmts.len() {
+					return Err(Error::new(
+						item_fn.block.span(),
+						"Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the \
+						last statement of your benchmark function definition if you have \
+						defined a return type. You should return something compatible \
+						with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement \
+						or change your signature to a blank return type.",
+					))
+				}
+				let Some(stmt) = item_fn.block.stmts.last() else { return missing_call(item_fn) };
+				(
+					Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len() - 1]),
+					Some(stmt.clone()),
+				)
+			},
 		};
 
 		Ok(BenchmarkDef {
 			params,
 			setup_stmts: Vec::from(&item_fn.block.stmts[0..i]),
 			call_def,
-			verify_stmts: Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]),
+			verify_stmts,
+			last_stmt,
 			extra,
 			skip_meta,
+			fn_sig: item_fn.sig.clone(),
+			fn_vis: item_fn.vis.clone(),
+			fn_attrs: item_fn.attrs.clone(),
 		})
 	}
 }
@@ -643,6 +741,7 @@ fn expand_benchmark(
 	let traits = quote!(#krate::frame_support::traits);
 	let setup_stmts = benchmark_def.setup_stmts;
 	let verify_stmts = benchmark_def.verify_stmts;
+	let last_stmt = benchmark_def.last_stmt;
 	let test_ident = Ident::new(format!("test_{}", name.to_string()).as_str(), Span::call_site());
 
 	// unroll params (prepare for quoting)
@@ -661,7 +760,8 @@ fn expand_benchmark(
 		true => quote!(T: Config<I>, I: 'static),
 	};
 
-	let (pre_call, post_call) = match benchmark_def.call_def {
+	// used in the benchmarking impls
+	let (pre_call, post_call, fn_call_body) = match &benchmark_def.call_def {
 		BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => {
 			let mut expr_call = expr_call.clone();
 
@@ -705,36 +805,97 @@ fn expand_benchmark(
 				qself: None,
 				path: Path { leading_colon: None, segments: punct },
 			});
-
+			let pre_call = quote! {
+				let __call = Call::<#type_use_generics>::#expr_call;
+				let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
+			};
+			let post_call = quote! {
+				let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
+					::decode(&mut &__benchmarked_call_encoded[..])
+					.expect("call is encoded above, encoding must be correct");
+				let __origin = #origin.into();
+				<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
+					__call_decoded,
+					__origin,
+				)
+			};
 			(
-				// (pre_call, post_call):
-				quote! {
-					let __call = Call::<#type_use_generics>::#expr_call;
-					let __benchmarked_call_encoded = #codec::Encode::encode(&__call);
-				},
+				// (pre_call, post_call, fn_call_body):
+				pre_call.clone(),
+				quote!(#post_call?;),
 				quote! {
-					let __call_decoded = <Call<#type_use_generics> as #codec::Decode>
-						::decode(&mut &__benchmarked_call_encoded[..])
-						.expect("call is encoded above, encoding must be correct");
-					let __origin = #origin.into();
-					<Call<#type_use_generics> as #traits::UnfilteredDispatchable>::dispatch_bypass_filter(
-						__call_decoded,
-						__origin,
-					)?;
+					#pre_call
+					#post_call.unwrap();
 				},
 			)
 		},
-		BenchmarkCallDef::Block { block, attr_span: _ } => (quote!(), quote!(#block)),
+		BenchmarkCallDef::Block { block, attr_span: _ } =>
+			(quote!(), quote!(#block), quote!(#block)),
+	};
+
+	let vis = benchmark_def.fn_vis;
+
+	// remove #[benchmark] attribute
+	let fn_attrs: Vec<&Attribute> = benchmark_def
+		.fn_attrs
+		.iter()
+		.filter(|attr| !syn::parse2::<keywords::benchmark>(attr.path.to_token_stream()).is_ok())
+		.collect();
+
+	// modify signature generics, ident, and inputs, e.g:
+	// before: `fn bench(u: Linear<1, 100>) -> Result<(), BenchmarkError>`
+	// after: `fn _bench <T: Config<I>, I: 'static>(u: u32, verify: bool) -> Result<(),
+	// BenchmarkError>`
+	let mut sig = benchmark_def.fn_sig;
+	sig.generics = parse_quote!(<#type_impl_generics>);
+	if !where_clause.is_empty() {
+		sig.generics.where_clause = parse_quote!(where #where_clause);
+	}
+	sig.ident =
+		Ident::new(format!("_{}", name.to_token_stream().to_string()).as_str(), Span::call_site());
+	let mut fn_param_inputs: Vec<TokenStream2> =
+		param_names.iter().map(|name| quote!(#name: u32)).collect();
+	fn_param_inputs.push(quote!(verify: bool));
+	sig.inputs = parse_quote!(#(#fn_param_inputs),*);
+
+	// used in instance() impl
+	let impl_last_stmt = match &last_stmt {
+		Some(stmt) => quote!(#stmt),
+		None => quote!(Ok(())),
+	};
+
+	let fn_def = quote! {
+		#(
+			#fn_attrs
+		)*
+		#vis #sig {
+			#(
+				#setup_stmts
+			)*
+			#fn_call_body
+			if verify {
+				#(
+					#verify_stmts
+				)*
+			}
+			#last_stmt
+		}
 	};
 
 	// generate final quoted tokens
 	let res = quote! {
+		// benchmark function definition
+		#fn_def
+
 		// compile-time assertions that each referenced param type implements ParamRange
 		#(
 			#home::assert_impl_all!(#param_types: #home::ParamRange);
 		)*
 
 		#[allow(non_camel_case_types)]
+		#(
+			#fn_attrs
+		)*
 		struct #name;
 
 		#[allow(unused_variables)]
@@ -773,7 +934,7 @@ fn expand_benchmark(
 							#verify_stmts
 						)*
 					}
-					Ok(())
+					#impl_last_stmt
 				}))
 			}
 		}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5e332801df8307ee1a9c3c23e21a02edf37a15bf
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs
@@ -0,0 +1,19 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() -> Result<(), BenchmarkException> {
+		let a = 2 + 2;
+		#[block]
+		{}
+		assert_eq!(a, 4);
+		Ok(())
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..ab0bff54a8a0333bd0e80d9aedb16e2c771b3a4f
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr
@@ -0,0 +1,5 @@
+error: expected `BenchmarkError`
+  --> tests/benchmark_ui/bad_return_non_benchmark_err.rs:10:27
+   |
+10 |     fn bench() -> Result<(), BenchmarkException> {
+   |                              ^^^^^^^^^^^^^^^^^^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a4b0d007eeecb218daf7cbbceabca52c2afbdf83
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs
@@ -0,0 +1,17 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benchmarks {
+	use super::*;
+
+	#[benchmark]
+	fn bench() -> (String, u32) {
+		#[block]
+		{}
+		(String::from("hey"), 23)
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..69d61b4229155e7092ef072d6fe4e8010de50df6
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr
@@ -0,0 +1,5 @@
+error: Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions
+  --> tests/benchmark_ui/bad_return_non_type_path.rs:10:16
+   |
+10 |     fn bench() -> (String, u32) {
+   |                   ^^^^^^^^^^^^^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs
new file mode 100644
index 0000000000000000000000000000000000000000..15289c298aec1558419a6ba9b41b8f36e9905eb9
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs
@@ -0,0 +1,15 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benchmarks {
+	#[benchmark]
+	fn bench() -> Result<u32, BenchmarkError> {
+		#[block]
+		{}
+		Ok(10)
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..4181ea099a14ffdfa4814a1d1b05d7d74a659c82
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr
@@ -0,0 +1,5 @@
+error: expected `()`
+ --> tests/benchmark_ui/bad_return_non_unit_t.rs:8:23
+  |
+8 |     fn bench() -> Result<u32, BenchmarkError> {
+  |                          ^^^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a6a2c61127fa2c03d11268db9659ec8a659c4bd0
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs
@@ -0,0 +1,22 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	fn something() -> Result<(), BenchmarkError> {
+		Ok(())
+	}
+
+	#[benchmark]
+	fn bench() {
+		something()?;
+		#[block]
+		{}
+		assert_eq!(2 + 2, 4);
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..1f8c9f1e171eb459312f62b4cbe3d240f1405730
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr
@@ -0,0 +1,10 @@
+error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
+  --> tests/benchmark_ui/bad_return_type_blank_with_question.rs:15:14
+   |
+5  | #[benchmarks]
+   | ------------- this function should return `Result` or `Option` to accept `?`
+...
+15 |         something()?;
+   |                    ^ cannot use the `?` operator in a function that returns `()`
+   |
+   = help: the trait `FromResidual<Result<std::convert::Infallible, frame_benchmarking::BenchmarkError>>` is not implemented for `()`
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs
new file mode 100644
index 0000000000000000000000000000000000000000..76f1299005309832dd834327ccdd60b26e6d85a9
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs
@@ -0,0 +1,16 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() -> Result<(), BenchmarkError> {
+		#[block]
+		{}
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..ff501a620fe33fad0b67eac73d16396cfc9f6107
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr
@@ -0,0 +1,9 @@
+error: Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the last statement of your benchmark function definition if you have defined a return type. You should return something compatible with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement or change your signature to a blank return type.
+  --> tests/benchmark_ui/bad_return_type_no_last_stmt.rs:10:43
+   |
+10 |       fn bench() -> Result<(), BenchmarkError> {
+   |  ______________________________________________^
+11 | |         #[block]
+12 | |         {}
+13 | |     }
+   | |_____^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c206ec36a151e24f4a91d62680e6436a01e8c032
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs
@@ -0,0 +1,19 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench(y: Linear<1, 2>) -> String {
+		let a = 2 + 2;
+		#[block]
+		{}
+		assert_eq!(a, 4);
+		String::from("test")
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..b830b8eb59c636fe5a517271b9bd4919cc47bd8f
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr
@@ -0,0 +1,5 @@
+error: expected `Result`
+  --> tests/benchmark_ui/bad_return_type_non_result.rs:10:31
+   |
+10 |     fn bench(y: Linear<1, 2>) -> String {
+   |                                  ^^^^^^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4b55885939747113ae1c715779c881148d142ce5
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs
@@ -0,0 +1,18 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() -> Option<BenchmarkError> {
+		#[block]
+		{}
+		assert_eq!(2 + 2, 4);
+		None
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..050da1676735a55c849650b84ed821f216d50ac3
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr
@@ -0,0 +1,5 @@
+error: expected `Result`
+  --> tests/benchmark_ui/bad_return_type_option.rs:10:16
+   |
+10 |     fn bench() -> Option<BenchmarkError> {
+   |                   ^^^^^^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/empty_function.rs b/substrate/frame/support/test/tests/benchmark_ui/empty_function.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bc04101dd384a04409b7d2ab26a413d05435afd4
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/empty_function.rs
@@ -0,0 +1,13 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() {}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/empty_function.stderr b/substrate/frame/support/test/tests/benchmark_ui/empty_function.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..69d75303613d9ae5d88d71414eb626d5d3f6169a
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/empty_function.stderr
@@ -0,0 +1,5 @@
+error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body.
+  --> tests/benchmark_ui/empty_function.rs:10:13
+   |
+10 |     fn bench() {}
+   |                ^^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/missing_call.rs b/substrate/frame/support/test/tests/benchmark_ui/missing_call.rs
index bc04101dd384a04409b7d2ab26a413d05435afd4..f39e74286b5cbe84e9cfa16ee3526f552730621d 100644
--- a/substrate/frame/support/test/tests/benchmark_ui/missing_call.rs
+++ b/substrate/frame/support/test/tests/benchmark_ui/missing_call.rs
@@ -7,7 +7,9 @@ mod benches {
 	use super::*;
 
 	#[benchmark]
-	fn bench() {}
+	fn bench() {
+		assert_eq!(2 + 2, 4);
+	}
 }
 
 fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/missing_call.stderr b/substrate/frame/support/test/tests/benchmark_ui/missing_call.stderr
index 3b55f77d42562766ce1f2a7dc4c617ee55716b13..908d9704392271d02b136237b53859dae8ab8e78 100644
--- a/substrate/frame/support/test/tests/benchmark_ui/missing_call.stderr
+++ b/substrate/frame/support/test/tests/benchmark_ui/missing_call.stderr
@@ -1,5 +1,8 @@
 error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body.
   --> tests/benchmark_ui/missing_call.rs:10:13
    |
-10 |     fn bench() {}
-   |                ^^
+10 |       fn bench() {
+   |  ________________^
+11 | |         assert_eq!(2 + 2, 4);
+12 | |     }
+   | |_____^
diff --git a/substrate/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4930aedd6011e6b63848a825ae368935e71b0b6e
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs
@@ -0,0 +1,17 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> {
+		#[block]
+		{}
+		Ok(())
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ce09b437a83bd42eb023a321d9ea1e913011a327
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs
@@ -0,0 +1,16 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() {
+		#[block]
+		{}
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4930aedd6011e6b63848a825ae368935e71b0b6e
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs
@@ -0,0 +1,17 @@
+use frame_benchmarking::v2::*;
+#[allow(unused_imports)]
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> {
+		#[block]
+		{}
+		Ok(())
+	}
+}
+
+fn main() {}
diff --git a/substrate/frame/support/test/tests/benchmark_ui/pass/valid_result.rs b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_result.rs
new file mode 100644
index 0000000000000000000000000000000000000000..33d71ece4a01812e61a7489d7cce72790050fd64
--- /dev/null
+++ b/substrate/frame/support/test/tests/benchmark_ui/pass/valid_result.rs
@@ -0,0 +1,18 @@
+use frame_benchmarking::v2::*;
+use frame_support_test::Config;
+
+#[benchmarks]
+mod benches {
+	use super::*;
+
+	#[benchmark]
+	fn bench() -> Result<(), BenchmarkError> {
+		let a = 2 + 2;
+		#[block]
+		{}
+		assert_eq!(a, 4);
+		Ok(())
+	}
+}
+
+fn main() {}