referrerpolicy=no-referrer-when-downgrade

pallet_revive/
benchmarking.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//! Benchmarks for the revive pallet.
19
20#![cfg(feature = "runtime-benchmarks")]
21use crate::{
22	Pallet as Contracts,
23	access_list::{AccessEntry, AccessList, MAX_ACCESS_LIST_ENTRIES, StorageOp, Warmth},
24	call_builder::{CallSetup, Contract, VmBinaryModule, caller_funding, default_deposit_limit},
25	evm::{
26		TransactionLegacyUnsigned, TransactionSigned, TransactionUnsigned,
27		block_hash::EthereumBlockBuilder, block_storage,
28	},
29	exec::{Ext, Key, Origin as ExecOrigin, PrecompileExt},
30	limits::{self, EVM_STACK_LIMIT},
31	precompiles::{
32		self, BenchmarkStorage, BenchmarkSystem, BuiltinPrecompile,
33		alloy::sol_types::{
34			SolType,
35			sol_data::{Bool, Bytes, FixedBytes, Uint},
36		},
37		run::builtin as run_builtin_precompile,
38	},
39	storage::WriteOutcome,
40	vm::{
41		evm,
42		evm::{ExtBytecode, Halt, Interpreter, instructions, instructions::utility::IntoAddress},
43		pvm,
44	},
45	*,
46};
47use alloc::{vec, vec::Vec};
48use alloy_core::sol_types::{SolInterface, SolValue};
49use codec::{Encode, MaxEncodedLen};
50use core::ops::ControlFlow;
51use frame_benchmarking::v2::*;
52use frame_support::{
53	self, assert_ok,
54	migrations::SteppedMigration,
55	storage::child,
56	traits::{Hooks, fungible::InspectHold},
57	weights::{Weight, WeightMeter},
58};
59use frame_system::RawOrigin;
60use k256::ecdsa::SigningKey;
61use pallet_revive_uapi::{
62	CallFlags, ReturnErrorCode, StorageFlags, pack_hi_lo,
63	precompiles::{storage::IStorage, system::ISystem},
64};
65use rand::{Rng, SeedableRng, seq::SliceRandom};
66use rand_pcg::Pcg64;
67use revm::bytecode::{
68	Bytecode,
69	opcode::{ADDMOD, DIV, MOD, MULMOD, POP, SDIV, SMOD},
70};
71use sp_consensus_aura::AURA_ENGINE_ID;
72use sp_consensus_babe::{
73	BABE_ENGINE_ID,
74	digests::{PreDigest, PrimaryPreDigest},
75};
76use sp_consensus_slots::Slot;
77use sp_runtime::{generic::DigestItem, traits::Zero};
78
79/// How many runs we do per API benchmark.
80///
81/// This is picked more or less arbitrary. We experimented with different numbers until
82/// the results appeared to be stable. Reducing the number would speed up the benchmarks
83/// but might make the results less precise.
84const API_BENCHMARK_RUNS: u32 = 1600;
85
86macro_rules! memory(
87	($($bytes:expr,)*) => {{
88		vec![].iter()$(.chain($bytes.iter()))*.cloned().collect::<Vec<_>>()
89	}};
90);
91
92macro_rules! build_runtime(
93	($runtime:ident, $memory:ident: [$($segment:expr,)*]) => {
94		build_runtime!($runtime, _contract, $memory: [$($segment,)*]);
95	};
96	($runtime:ident, $contract:ident, $memory:ident: [$($bytes:expr,)*]) => {
97		build_runtime!($runtime, $contract);
98		let mut $memory = memory!($($bytes,)*);
99	};
100	($runtime:ident, $contract:ident) => {
101		let mut setup = CallSetup::<T>::default();
102		let $contract = setup.contract();
103		let input = setup.data();
104		let (mut ext, _) = setup.ext();
105		let mut $runtime = $crate::vm::pvm::Runtime::<_, [u8]>::new(&mut ext, input);
106	};
107);
108
109/// Get the pallet account and whitelist it for benchmarking.
110/// The account is warmed up `on_initialize` so read should not impact the PoV.
111fn whitelisted_pallet_account<T: Config>() -> T::AccountId {
112	let pallet_account = Pallet::<T>::account_id();
113	whitelist_account!(pallet_account);
114	pallet_account
115}
116
117/// Delegate `address` to `target` (EIP-7702) without going through signature recovery.
118///
119/// Returns the account id the `seal_call` family expects in guest memory: `address` is never
120/// mapped, so it resolves to its fallback account whose first 20 encoded bytes are `address`.
121fn delegated_eoa<T: Config>(address: H160, target: H160) -> Result<T::AccountId, BenchmarkError> {
122	let account_id = T::AddressMapper::to_fallback_account_id(&address);
123	AccountInfo::<T>::set_delegation(&address, Some(target), &account_id)
124		.map_err(|_| "set_delegation failed")?;
125	Ok(account_id)
126}
127
128/// Pushes the data a benchmark set up out of the L1 and L2 caches by writing unrelated memory.
129///
130/// Writes 8 MiB, which is larger than the L2 cache of the reference hardware.
131fn evict_caches() {
132	const EVICTION_SIZE: usize = 8 * 1024 * 1024;
133	core::hint::black_box(vec![1u8; EVICTION_SIZE]);
134}
135
136/// Pushes `values` onto the interpreter's stack in order, so the last value ends up on top.
137fn setup_stack<E: Ext>(interpreter: &mut Interpreter<E>, values: impl IntoIterator<Item = U256>) {
138	values
139		.into_iter()
140		.for_each(|value| interpreter.stack.push(value).continue_value().unwrap());
141}
142
143/// Pairs a pseudo-randomly chosen denominator with the shared `2^255` numerator.
144fn unsigned_division_operands(rng: &mut impl Rng, denominators: &[U256]) -> [U256; 2] {
145	let denominator = denominators.choose(rng).copied().unwrap();
146	[denominator, knuth_division_operands::NUMERATOR]
147}
148
149/// Like [`unsigned_division_operands`], but negates the divisor pseudo-randomly.
150fn signed_division_operands(rng: &mut impl Rng, denominators: &[U256]) -> [U256; 2] {
151	let [magnitude, numerator] = unsigned_division_operands(rng, denominators);
152	let divisor =
153		if rng.gen_bool(0.5) { magnitude } else { U256::zero().overflowing_sub(magnitude).0 };
154	[divisor, numerator]
155}
156
157/// Operands covering all 17 correction patterns possible when dividing `2^255` by two-limb
158/// divisors.
159///
160/// Knuth's Algorithm D can correct the highest quotient limb zero or one time and each lower limb
161/// up to twice. The pattern `1, 0, 2` is impossible for this numerator. The counts were checked
162/// against `uint` 0.10.0's division. None of these operands needs the rarer correction that adds
163/// the divisor back after subtracting.
164///
165/// Picking a denominator pseudo-randomly varies the correction branches across divisions. Each
166/// denominator also makes the `u128` divisions behind the estimates take as many steps as any we
167/// found with its pattern. Correcting a limb twice needs a second denominator limb above one, which
168/// shortens the division behind the first estimate.
169///
170/// The fixed divisors are the denominators that measured slowest for each op-code. The slowest one
171/// depends on the op-code: `SDIV` and `SMOD` measured slowest with different patterns than `DIV`
172/// and `MOD`. After updating `uint` or the Rust toolchain, search for the denominators again and
173/// measure which one is slowest for each op-code.
174mod knuth_division_operands {
175	use super::{Rng, U256};
176
177	pub fn fixed_signed_operands(rng: &mut impl Rng, magnitude: U256) -> [U256; 2] {
178		// Consume the divisor draw to match the baseline's pseudo-random sign sequence.
179		let _ = rng.gen_range(0..DENOMINATORS.len() as u32);
180		let divisor =
181			if rng.gen_bool(0.5) { magnitude } else { U256::zero().overflowing_sub(magnitude).0 };
182		[divisor, NUMERATOR]
183	}
184
185	/// 2^255 as an unsigned value, or -2^255 as a signed one, so signed and unsigned divisions
186	/// divide the same magnitudes.
187	pub const NUMERATOR: U256 = U256([0, 0, 0, 1 << 63]);
188	/// The denominator that measured slowest for `DIV`.
189	pub const DIV_FIXED_DIVISOR: U256 = U256([0x05f0_ac84_84d2_a8f7, 0x1, 0, 0]);
190	/// The denominator that measured slowest for `SDIV`.
191	pub const SDIV_FIXED_DIVISOR: U256 = U256([0x16d5_ec4a_0e7b_8cdb, 0x1, 0, 0]);
192	/// The denominator that measured slowest for `MOD`.
193	pub const MOD_FIXED_DIVISOR: U256 = U256([0x0007_f84c_6624_4131, 0x1, 0, 0]);
194	/// The denominator that measured slowest for `SMOD`.
195	pub const SMOD_FIXED_DIVISOR: U256 = U256([0x5141_1d54_0d50_9ec1, 0x1, 0, 0]);
196	/// `ADDMOD` reuses the divisor of `MOD`.
197	pub const ADDMOD_FIXED_DIVISOR: U256 = MOD_FIXED_DIVISOR;
198
199	/// Two distinct divisors per pattern, with correction counts listed in limb order 2, 1, 0.
200	pub const DENOMINATORS: [U256; 34] = [
201		// Corrections: 0, 0, 0.
202		U256([0x1ac6_6dea_3270_0980, 0x1, 0, 0]),
203		U256([0x1150_77a4_12e2_ccc0, 0x1, 0, 0]),
204		// Corrections: 0, 0, 1.
205		U256([0x3c9b_ee44_0b3f_dba7, 0x1, 0, 0]),
206		U256([0x62aa_fc46_0845_aad9, 0x1, 0, 0]),
207		// Corrections: 0, 1, 0.
208		U256([0x2806_4b94_70ef_f3d9, 0x1, 0, 0]),
209		U256([0x61d1_c4c6_195a_2d43, 0x1, 0, 0]),
210		// Corrections: 0, 1, 1.
211		SDIV_FIXED_DIVISOR,
212		U256([0x3ef8_7550_1dce_eb99, 0x1, 0, 0]),
213		// Corrections: 1, 0, 0.
214		U256([0xce1e_8ac2_290c_265f, 0x1, 0, 0]),
215		U256([0xd736_05ee_66e1_a76b, 0x1, 0, 0]),
216		// Corrections: 1, 0, 1.
217		U256([0xeb2b_fb00_8fa9_e0f7, 0x1, 0, 0]),
218		SMOD_FIXED_DIVISOR,
219		// Corrections: 1, 1, 0.
220		U256([0x500c_237e_4578_d88b, 0x1, 0, 0]),
221		U256([0x0164_a34e_0b1c_a4fd, 0x1, 0, 0]),
222		// Corrections: 1, 1, 1.
223		MOD_FIXED_DIVISOR,
224		DIV_FIXED_DIVISOR,
225		// Corrections: 0, 0, 2.
226		U256([0x3db2_354e_f8e8_6ca5, 0x96aa_3b25, 0, 0]),
227		U256([0x2848_08be_ea22_56a3, 0x9339_ddd9, 0, 0]),
228		// Corrections: 0, 1, 2.
229		U256([0x2534_a796_ffdf_b85e, 0xc250_8262, 0, 0]),
230		U256([0x4726_e939_da3c_08fe, 0xc198_a4ce, 0, 0]),
231		// Corrections: 0, 2, 0.
232		U256([0x5d5b_2f53_f03d_bec8, 0x909d_ad3b, 0, 0]),
233		U256([0x8e31_4add_736d_ee08, 0x40ce_7f0c, 0, 0]),
234		// Corrections: 0, 2, 1.
235		U256([0x0c62_030f_fabf_0fc7, 0xb51d_e6cb, 0, 0]),
236		U256([0x0332_58df_e522_494c, 0xb4db_915d, 0, 0]),
237		// Corrections: 0, 2, 2.
238		U256([0x0b07_4c7e_fb00_0c1f, 0xa5b9_8f9a, 0, 0]),
239		U256([0x26a5_a3c7_fce7_abd6, 0xa4a5_d36a, 0, 0]),
240		// Corrections: 1, 1, 2.
241		U256([0x6654_d104_1c8e_6943, 0x2, 0, 0]),
242		U256([0x64b6_7f9c_0cf1_dbff, 0x2, 0, 0]),
243		// Corrections: 1, 2, 0.
244		U256([0xeff9_3d22_eeac_89cf, 0x87, 0, 0]),
245		U256([0xadb2_0f65_e0ba_c8df, 0x12, 0, 0]),
246		// Corrections: 1, 2, 1.
247		U256([0xf486_a407_463b_dfde, 0x253, 0, 0]),
248		U256([0xaea8_360c_3043_f5f9, 0x1b4, 0, 0]),
249		// Corrections: 1, 2, 2.
250		U256([0xecbe_4a28_bb14_dbf0, 0x43, 0, 0]),
251		U256([0x64e2_acbc_9934_b1f2, 0x43, 0, 0]),
252	];
253}
254
255/// Operands covering all eight correction patterns in the one-limb division path.
256///
257/// `div_mod_small` divides one numerator limb at a time. The highest limb cannot require a
258/// correction. Each remaining limb can cause one correction in the 128-bit division helper. The
259/// patterns below are ordered by numerator limbs 2, 1, 0. The counts were checked with a
260/// source-level trace of Rust 1.93 `compiler_builtins` using the shared `2^255` numerator.
261///
262/// Each pattern has two distinct full-width divisors. Selecting a divisor pseudo-randomly varies
263/// the correction branches while preserving the signed minimum-numerator case.
264mod one_limb_division_operands {
265	use super::U256;
266
267	/// Two distinct divisors per pattern, with correction counts listed in limb order 2, 1, 0.
268	pub const DENOMINATORS: [U256; 16] = [
269		// Corrections: 0, 0, 0.
270		U256([0xfff9_9928_4b39_14e9, 0, 0, 0]),
271		U256([0xfff7_2ef6_07d7_0402, 0, 0, 0]),
272		// Corrections: 0, 0, 1.
273		U256([0xfffc_adff_2ef2_7bf4, 0, 0, 0]),
274		U256([0xffc7_1287_6d60_c1f0, 0, 0, 0]),
275		// Corrections: 0, 1, 0.
276		U256([0xfff8_74b0_648f_da8a, 0, 0, 0]),
277		U256([0xfff7_1d06_39ab_3877, 0, 0, 0]),
278		// Corrections: 0, 1, 1.
279		U256([0xf974_5f46_74c9_5adf, 0, 0, 0]),
280		U256([0xf7e1_3204_7bd0_15af, 0, 0, 0]),
281		// Corrections: 1, 0, 0.
282		U256([0xfff7_ac60_75f1_501f, 0, 0, 0]),
283		U256([0xffe8_555d_5f54_a346, 0, 0, 0]),
284		// Corrections: 1, 0, 1.
285		U256([0xfe7a_ca58_4afc_ed70, 0, 0, 0]),
286		U256([0xfd2b_4905_64bd_9797, 0, 0, 0]),
287		// Corrections: 1, 1, 0.
288		U256([0xfcd0_0e3d_ccbd_7da4, 0, 0, 0]),
289		U256([0xee06_4d5d_bfab_b524, 0, 0, 0]),
290		// Corrections: 1, 1, 1.
291		U256([0xf42a_21ef_e080_fc89, 0, 0, 0]),
292		U256([0xf095_aea7_fc96_fbde, 0, 0, 0]),
293	];
294}
295
296/// Randomly chooses between the Knuth and one-limb correction-pattern operand sets.
297mod mixed_division_operands {
298	use super::{
299		Rng, U256, knuth_division_operands, one_limb_division_operands, signed_division_operands,
300		unsigned_division_operands,
301	};
302
303	pub fn unsigned_operands(rng: &mut impl Rng) -> [U256; 2] {
304		let denominators = denominators(rng);
305		unsigned_division_operands(rng, denominators)
306	}
307
308	pub fn signed_operands(rng: &mut impl Rng) -> [U256; 2] {
309		let denominators = denominators(rng);
310		signed_division_operands(rng, denominators)
311	}
312
313	fn denominators(rng: &mut impl Rng) -> &'static [U256] {
314		if rng.gen_bool(0.5) {
315			&knuth_division_operands::DENOMINATORS
316		} else {
317			&one_limb_division_operands::DENOMINATORS
318		}
319	}
320}
321
322#[benchmarks(
323	where
324		T: Config,
325		<T as Config>::RuntimeCall: From<frame_system::Call<T>>,
326		<T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
327		OriginFor<T>: From<Origin<T>>,
328)]
329mod benchmarks {
330	use super::*;
331
332	/// The base weight consumed on processing contracts deletion queue.
333	#[benchmark(pov_mode = Measured)]
334	fn deletion_queue_batch() {
335		#[block]
336		{
337			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
338		}
339	}
340
341	// Benchmark for processing N EIP-7702 authorizations with empty accounts
342	// This measures the overhead of processing the authorization list
343	// Parameter `n`: number of authorizations to process
344	#[benchmark(pov_mode = Measured)]
345	fn process_new_account_authorization(n: Linear<0, 255>) -> Result<(), BenchmarkError> {
346		use crate::evm::eip7702;
347		use sp_io::hashing::keccak_256;
348
349		let caller: T::AccountId = whitelisted_caller();
350		T::Currency::set_balance(&caller, caller_funding::<T>());
351		<T as Config>::FeeInfo::deposit_txfee(
352			<T as Config>::Currency::issue(caller_funding::<T>()),
353		);
354		let chain_id = U256::from(T::ChainId::get());
355		let exec_config = ExecConfig::new_eth_tx(U256::from(1), 0, Weight::MAX);
356
357		// Worst case: every authorization targets a *distinct* contract with *distinct* code
358		// so neither `AccountInfoOf<target>` nor `CodeInfoOf<code_hash>` reads can be cached
359		// across the loop. `dummy_unique(i)` produces a unique blob per index; the contract is
360		// deployed at a unique salt-index so addresses also differ.
361		let mut authorization_list = vec![];
362		for i in 0..n {
363			let target_contract =
364				Contract::<T>::with_index(i + 1, VmBinaryModule::dummy_unique(i), vec![])?;
365			let target = target_contract.address;
366
367			let key_material = keccak_256(&i.to_le_bytes());
368			let key = SigningKey::from_bytes(&key_material.into()).expect("valid key; qed");
369			let signed_auth = eip7702::sign_authorization(&key, chain_id, target, U256::zero());
370			authorization_list.push(signed_auth);
371		}
372
373		let auth_result;
374		#[block]
375		{
376			auth_result =
377				eip7702::process_authorizations::<T>(&authorization_list, &caller, &exec_config);
378		}
379
380		assert_eq!(auth_result.new_accounts, n as u32, "All authorizations should be new");
381		Ok(())
382	}
383
384	// Benchmark for processing N EIP-7702 authorizations with existing accounts.
385	//
386	// Worst case: each authority is *already* delegated to a unique target whose code is
387	// referenced only by that delegation, with the deposit paid by an account other than the
388	// bench's `caller`. Re-delegating to a fresh target then exercises the most expensive paths
389	// per auth:
390	//   - payer-change: full refund to `setup_payer` + full charge from `caller`
391	//   - old-code refcount → 0: `decrement_refcount` removes `CodeInfoOf` + `PristineCode`
392	//   - new-code refcount 0 → 1: `increment_refcount`
393	//
394	// To produce `refcount(old_code) == 1` going into the bench, we directly decrement the
395	// deployment's contribution after setup. This is equivalent to having terminated the old
396	// target (which would also drop the deployment's ref) — `#[block]` only reads the
397	// delegation snapshot's `code_hash`, never `AccountInfoOf[old_target]`, so it doesn't
398	// matter that the latter is still present.
399	//
400	// Parameter `n`: number of authorizations to process
401	#[benchmark(pov_mode = Measured)]
402	fn process_existing_account_authorization(n: Linear<0, 255>) -> Result<(), BenchmarkError> {
403		use crate::evm::eip7702;
404		use sp_io::hashing::keccak_256;
405
406		let caller: T::AccountId = whitelisted_caller();
407		T::Currency::set_balance(&caller, caller_funding::<T>());
408		<T as Config>::FeeInfo::deposit_txfee(
409			<T as Config>::Currency::issue(caller_funding::<T>()),
410		);
411
412		// Distinct payer (≠ `caller`) so the bench takes the payer-change branch in
413		// `process_authorizations` (full refund + full charge per auth) rather than the
414		// same-payer net-diff fast path.
415		let setup_payer: T::AccountId = account("setup_payer", 0, 0);
416		T::Currency::set_balance(&setup_payer, caller_funding::<T>());
417		<T as Config>::FeeInfo::deposit_txfee(
418			<T as Config>::Currency::issue(caller_funding::<T>()),
419		);
420
421		let chain_id = U256::from(T::ChainId::get());
422		let exec_config = ExecConfig::new_eth_tx(U256::from(1), 0, Weight::MAX);
423
424		let mut authorization_list = vec![];
425		for i in 0..n {
426			// Old delegation target with unique code (so each gets its own `CodeInfoOf` entry).
427			let old_target =
428				Contract::<T>::with_index(2 * i + 1, VmBinaryModule::dummy_unique(2 * i), vec![])?;
429			let old_code_hash = <AccountInfoOf<T>>::get(&old_target.address)
430				.and_then(|info| match info.account_type {
431					AccountType::Contract(c) => Some(c.code_hash),
432					_ => None,
433				})
434				.ok_or("old_target should be a Contract")?;
435
436			// Pre-existing delegation paid by `setup_payer`. Bumps the authority's nonce to 1
437			// and brings `refcount(old_code)` to 2 (deployment + delegation snapshot).
438			let key_material = keccak_256(&i.to_le_bytes());
439			let key = SigningKey::from_bytes(&key_material.into()).expect("valid key; qed");
440			let setup_auth =
441				eip7702::sign_authorization(&key, chain_id, old_target.address, U256::zero());
442			let _ = eip7702::process_authorizations::<T>(&[setup_auth], &setup_payer, &exec_config);
443
444			// Drop the deployment's ref so the delegation snapshot is the sole holder. This
445			// mirrors the post-termination storage state without spending setup time on real
446			// contract calls (the bench measures `#[block]`, not setup).
447			let _ =
448				CodeInfo::<T>::decrement_refcount(old_code_hash).map_err(|_| "decrement failed")?;
449
450			// New target (also unique code) that the authority re-delegates to.
451			let new_target = Contract::<T>::with_index(
452				2 * i + 2,
453				VmBinaryModule::dummy_unique(2 * i + 1),
454				vec![],
455			)?;
456
457			// Authority's nonce is 1 after setup, so the re-delegation auth signs with nonce=1.
458			let signed_auth =
459				eip7702::sign_authorization(&key, chain_id, new_target.address, U256::one());
460			authorization_list.push(signed_auth);
461		}
462
463		let auth_result;
464		#[block]
465		{
466			auth_result =
467				eip7702::process_authorizations::<T>(&authorization_list, &caller, &exec_config);
468		}
469
470		assert_eq!(auth_result.new_accounts, 0u32);
471		assert_eq!(auth_result.existing_accounts, n as u32);
472		Ok(())
473	}
474
475	// Measures the per-tuple cost of an authorization that runs through chain_id check
476	// and ecdsa_recover but then fails validation (here: nonce mismatch) — captures the
477	// sig-recovery cost without any account creation/update work.
478	#[benchmark(pov_mode = Measured)]
479	fn process_invalid_authorization(n: Linear<0, 255>) -> Result<(), BenchmarkError> {
480		use crate::evm::eip7702;
481		use sp_io::hashing::keccak_256;
482
483		let chain_id = U256::from(T::ChainId::get());
484		let target_contract = Contract::<T>::with_index(0, VmBinaryModule::dummy(), vec![])?;
485		let target = target_contract.address;
486		let caller: T::AccountId = whitelisted_caller();
487		T::Currency::set_balance(&caller, caller_funding::<T>());
488		<T as Config>::FeeInfo::deposit_txfee(
489			<T as Config>::Currency::issue(caller_funding::<T>()),
490		);
491		let exec_config = ExecConfig::new_eth_tx(U256::from(1), 0, Weight::MAX);
492
493		let mut authorization_list = vec![];
494		for i in 0..n {
495			let key_material = keccak_256(&(i as u32).to_le_bytes());
496			let key = SigningKey::from_bytes(&key_material.into()).expect("valid key; qed");
497			// Force nonce mismatch: signer's nonce is 0, but we sign nonce=1.
498			let signed_auth = eip7702::sign_authorization(&key, chain_id, target, U256::one());
499			authorization_list.push(signed_auth);
500		}
501
502		let auth_result;
503		#[block]
504		{
505			auth_result =
506				eip7702::process_authorizations::<T>(&authorization_list, &caller, &exec_config);
507		}
508
509		assert_eq!(auth_result.new_accounts, 0u32);
510		assert_eq!(auth_result.existing_accounts, 0u32);
511		Ok(())
512	}
513
514	/// Measures the per-entry cost of `process_deletion_queue_batch`: one `DeletionQueue` read
515	/// plus the `DeletionQueue` + `DeletionQueueCounter` writes done by `entry.remove()`.
516	#[benchmark(pov_mode = Measured)]
517	fn deletion_queue_per_entry() -> Result<(), BenchmarkError> {
518		let instance = Contract::<T>::with_storage(VmBinaryModule::dummy(), 0, 0)?;
519		ContractInfo::<T>::queue_for_deletion(
520			instance.info()?.trie_id,
521			instance.account_id.clone(),
522		);
523
524		#[block]
525		{
526			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
527		}
528
529		assert!(<DeletionQueue<T>>::iter().next().is_none(), "deletion queue should be drained",);
530		Ok(())
531	}
532
533	#[benchmark(skip_meta, pov_mode = Measured)]
534	fn deletion_queue_per_trie_key(k: Linear<0, 1024>) -> Result<(), BenchmarkError> {
535		let instance =
536			Contract::<T>::with_storage(VmBinaryModule::dummy(), k, limits::STORAGE_BYTES)?;
537		ContractInfo::<T>::queue_for_deletion(
538			instance.info()?.trie_id,
539			instance.account_id.clone(),
540		);
541
542		#[block]
543		{
544			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
545		}
546
547		assert!(<DeletionQueue<T>>::iter().next().is_none(), "deletion queue should be drained",);
548		Ok(())
549	}
550
551	/// Measures the cost of clearing one [`NativeDepositOf`] row during
552	/// [`ContractInfo::process_deletion_queue_batch`]. Pre-populates the contract with `k`
553	/// per-payer rows and queues the contract for deletion with `native_cleared = false` and
554	/// an empty trie. The deletion queue then drains all rows in one go.
555	#[benchmark(skip_meta, pov_mode = Measured)]
556	fn deletion_queue_per_native_deposit_key(k: Linear<0, 1024>) -> Result<(), BenchmarkError> {
557		use frame_benchmarking::v2::account;
558
559		// Empty trie: zero items, zero bytes; we only want to measure native-deposit cleanup.
560		let instance = Contract::<T>::with_storage(VmBinaryModule::dummy(), 0, 0)?;
561		for i in 0..k {
562			let payer: T::AccountId = account("payer", i, 0);
563			NativeDepositOf::<T>::insert(&instance.account_id, &payer, BalanceOf::<T>::default());
564		}
565		ContractInfo::<T>::queue_for_deletion(
566			instance.info()?.trie_id,
567			instance.account_id.clone(),
568		);
569
570		#[block]
571		{
572			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
573		}
574
575		assert!(<DeletionQueue<T>>::iter().next().is_none(), "deletion queue should be drained",);
576		Ok(())
577	}
578
579	// This benchmarks the overhead of loading a code of size `c` byte from storage and into
580	// the execution engine.
581	//
582	// `call_with_pvm_code_per_byte(c) - call_with_pvm_code_per_byte(0)`
583	//
584	// This does **not** include the actual execution for which the gas meter
585	// is responsible. The code used here will just return on call.
586	//
587	// We expect the influence of `c` to be none in this benchmark because every instruction that
588	// is not in the first basic block is never read. We are primarily interested in the
589	// `proof_size` result of this benchmark.
590	#[benchmark(pov_mode = Measured)]
591	fn call_with_pvm_code_per_byte(c: Linear<0, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
592		let instance =
593			Contract::<T>::with_caller(whitelisted_caller(), VmBinaryModule::sized(c), vec![])?;
594		let value = Pallet::<T>::min_balance();
595		let storage_deposit = default_deposit_limit::<T>();
596
597		#[extrinsic_call]
598		call(
599			RawOrigin::Signed(instance.caller.clone()),
600			instance.address,
601			value,
602			Weight::MAX,
603			storage_deposit,
604			vec![],
605		);
606
607		Ok(())
608	}
609
610	// This benchmarks the overhead of loading a code of size `c` byte from storage and into
611	// the execution engine.
612	/// This is similar to `call_with_pvm_code_per_byte` but for EVM bytecode.
613	#[benchmark(pov_mode = Measured)]
614	fn call_with_evm_code_per_byte(c: Linear<1, { 10 * 1024 }>) -> Result<(), BenchmarkError> {
615		let instance = Contract::<T>::with_caller(
616			whitelisted_caller(),
617			VmBinaryModule::evm_init_code_for_runtime_size(c),
618			vec![],
619		)?;
620		let value = Pallet::<T>::min_balance();
621		let storage_deposit = default_deposit_limit::<T>();
622
623		let code_len = PristineCode::<T>::get(instance.info()?.code_hash)
624			.expect("code should be stored")
625			.len();
626		assert_eq!(
627			code_len, c as usize,
628			"runtime bytecode should be exactly {c} bytes, got {code_len}"
629		);
630
631		#[extrinsic_call]
632		call(
633			RawOrigin::Signed(instance.caller.clone()),
634			instance.address,
635			value,
636			Weight::MAX,
637			storage_deposit,
638			vec![],
639		);
640
641		Ok(())
642	}
643
644	// Measure the amount of time it takes to compile a single basic block.
645	//
646	// (basic_block_compilation(1) - basic_block_compilation(0)).ref_time()
647	//
648	// This is needed because the interpreter will always compile a whole basic block at
649	// a time. To prevent a contract from triggering compilation without doing any execution
650	// we will always charge one max sized block per contract call.
651	//
652	// We ignore the proof size component when using this benchmark as this is already accounted
653	// for in `call_with_pvm_code_per_byte`.
654	#[benchmark(pov_mode = Measured)]
655	fn basic_block_compilation(b: Linear<0, 1>) -> Result<(), BenchmarkError> {
656		let instance = Contract::<T>::with_caller(
657			whitelisted_caller(),
658			VmBinaryModule::with_num_instructions(limits::code::BASIC_BLOCK_SIZE),
659			vec![],
660		)?;
661		let value = Pallet::<T>::min_balance();
662		let storage_deposit = default_deposit_limit::<T>();
663
664		#[block]
665		{
666			Pallet::<T>::call(
667				RawOrigin::Signed(instance.caller.clone()).into(),
668				instance.address,
669				value,
670				Weight::MAX,
671				storage_deposit,
672				vec![],
673			)?;
674		}
675
676		Ok(())
677	}
678
679	// `c`: Size of the code in bytes.
680	// `i`: Size of the input in bytes.
681	#[benchmark(pov_mode = Measured)]
682	fn instantiate_with_code(
683		c: Linear<0, { 100 * 1024 }>,
684		i: Linear<0, { limits::CALLDATA_BYTES }>,
685	) {
686		let pallet_account = whitelisted_pallet_account::<T>();
687		let input = vec![42u8; i as usize];
688		let salt = [42u8; 32];
689		let value = Pallet::<T>::min_balance();
690		let caller = whitelisted_caller();
691		T::Currency::set_balance(&caller, caller_funding::<T>());
692		let VmBinaryModule { code, .. } = VmBinaryModule::sized(c);
693		let origin = RawOrigin::Signed(caller.clone());
694		if !T::AddressMapper::is_mapped(&caller) {
695			T::AddressMapper::map(&caller).unwrap();
696		}
697		let deployer = T::AddressMapper::to_address(&caller);
698		let addr = crate::address::create2(&deployer, &code, &input, &salt);
699		let account_id = T::AddressMapper::to_fallback_account_id(&addr);
700		let storage_deposit = default_deposit_limit::<T>();
701		#[extrinsic_call]
702		_(origin, value, Weight::MAX, storage_deposit, code, input, Some(salt));
703
704		let deposit =
705			T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id);
706		// uploading the code reserves some balance in the pallet's account
707		let code_deposit = T::Currency::balance_on_hold(
708			&HoldReason::CodeUploadDepositReserve.into(),
709			&pallet_account,
710		);
711		let mapping_deposit =
712			T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &caller);
713		assert_eq!(
714			T::Currency::balance(&caller),
715			caller_funding::<T>() - value - deposit - code_deposit - mapping_deposit,
716		);
717		// contract has the full value
718		assert_eq!(T::Currency::balance(&account_id), value + Pallet::<T>::min_balance());
719	}
720
721	// `c`: Size of the code in bytes.
722	// `i`: Size of the input in bytes.
723	// `d`: with or without dust value to transfer
724	#[benchmark(pov_mode = Measured)]
725	fn eth_instantiate_with_code(
726		c: Linear<0, { 100 * 1024 }>,
727		i: Linear<0, { limits::CALLDATA_BYTES }>,
728		d: Linear<0, 1>,
729	) -> Result<(), BenchmarkError> {
730		let input = vec![42u8; i as usize];
731
732		// Use an `effective_gas_price` that is not a multiple of `T::NativeToEthRatio`
733		// to hit the code that charge the rounding error so that tx_cost == effective_gas_price *
734		// gas_used
735		let effective_gas_price = Pallet::<T>::evm_base_fee() + 1;
736		let value = Pallet::<T>::min_balance();
737		let dust = 42u32 * d;
738		let evm_value =
739			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
740		let caller = whitelisted_caller();
741		T::Currency::set_balance(&caller, caller_funding::<T>());
742		let VmBinaryModule { code, .. } = VmBinaryModule::sized(c);
743		let origin = Origin::EthTransaction(caller.clone());
744		if !T::AddressMapper::is_mapped(&caller) {
745			T::AddressMapper::map(&caller).unwrap();
746		}
747		let deployer = T::AddressMapper::to_address(&caller);
748		let nonce = System::<T>::account_nonce(&caller).try_into().unwrap_or_default();
749		let addr = crate::address::create1(&deployer, nonce);
750
751		assert!(AccountInfoOf::<T>::get(&deployer).is_none());
752
753		<T as Config>::FeeInfo::deposit_txfee(
754			<T as Config>::Currency::issue(caller_funding::<T>()),
755		);
756
757		#[extrinsic_call]
758		_(
759			origin,
760			evm_value,
761			Weight::MAX,
762			U256::MAX,
763			code,
764			input,
765			TransactionSigned::default().signed_payload(),
766			effective_gas_price,
767			0,
768		);
769
770		// contract has the full value
771		assert_eq!(Pallet::<T>::evm_balance(&addr), evm_value);
772		Ok(())
773	}
774
775	#[benchmark(pov_mode = Measured)]
776	fn deposit_eth_extrinsic_revert_event() {
777		#[block]
778		{
779			Pallet::<T>::deposit_event(Event::<T>::EthExtrinsicRevert {
780				dispatch_error: crate::Error::<T>::BenchmarkingError.into(),
781			});
782		}
783	}
784
785	// `i`: Size of the input in bytes.
786	// `s`: Size of e salt in bytes.
787	#[benchmark(pov_mode = Measured)]
788	fn instantiate(i: Linear<0, { limits::CALLDATA_BYTES }>) -> Result<(), BenchmarkError> {
789		let pallet_account = whitelisted_pallet_account::<T>();
790		let input = vec![42u8; i as usize];
791		let salt = [42u8; 32];
792		let value = Pallet::<T>::min_balance();
793		let caller = whitelisted_caller();
794		T::Currency::set_balance(&caller, caller_funding::<T>());
795		let origin = RawOrigin::Signed(caller.clone());
796		if !T::AddressMapper::is_mapped(&caller) {
797			T::AddressMapper::map(&caller).unwrap();
798		}
799		let VmBinaryModule { code, .. } = VmBinaryModule::dummy();
800		let storage_deposit = default_deposit_limit::<T>();
801		let deployer = T::AddressMapper::to_address(&caller);
802		let addr = crate::address::create2(&deployer, &code, &input, &salt);
803		let hash = Contracts::<T>::bare_upload_code(origin.clone().into(), code, storage_deposit)?
804			.code_hash;
805		let account_id = T::AddressMapper::to_fallback_account_id(&addr);
806
807		#[extrinsic_call]
808		_(origin, value, Weight::MAX, storage_deposit, hash, input, Some(salt));
809
810		let deposit =
811			T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id);
812		let code_deposit = T::Currency::balance_on_hold(
813			&HoldReason::CodeUploadDepositReserve.into(),
814			&pallet_account,
815		);
816		let mapping_deposit =
817			T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &account_id);
818		// value was removed from the caller
819		assert_eq!(
820			T::Currency::total_balance(&caller),
821			caller_funding::<T>() - value - deposit - code_deposit - mapping_deposit,
822		);
823		// contract has the full value
824		assert_eq!(T::Currency::balance(&account_id), value + Pallet::<T>::min_balance());
825
826		Ok(())
827	}
828
829	// We just call a dummy contract to measure the overhead of the call extrinsic.
830	// The size of the data has no influence on the costs of this extrinsic as long as the contract
831	// won't call `seal_call_data_copy` in its constructor to copy the data to contract memory.
832	// The dummy contract used here does not do this. The costs for the data copy is billed as
833	// part of `seal_call_data_copy`. The costs for invoking a contract of a specific size are not
834	// part of this benchmark because we cannot know the size of the contract when issuing a call
835	// transaction. See `call_with_pvm_code_per_byte` for this.
836	#[benchmark(pov_mode = Measured)]
837	fn call() -> Result<(), BenchmarkError> {
838		let pallet_account = whitelisted_pallet_account::<T>();
839		let data = vec![42u8; 1024];
840		let instance =
841			Contract::<T>::with_caller(whitelisted_caller(), VmBinaryModule::dummy(), vec![])?;
842		let value = Pallet::<T>::min_balance();
843		let origin = RawOrigin::Signed(instance.caller.clone());
844		let before = T::Currency::balance(&instance.account_id);
845		let storage_deposit = default_deposit_limit::<T>();
846		#[extrinsic_call]
847		_(origin, instance.address, value, Weight::MAX, storage_deposit, data);
848		let deposit = T::Currency::balance_on_hold(
849			&HoldReason::StorageDepositReserve.into(),
850			&instance.account_id,
851		);
852		let code_deposit = T::Currency::balance_on_hold(
853			&HoldReason::CodeUploadDepositReserve.into(),
854			&pallet_account,
855		);
856		let mapping_deposit =
857			T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &instance.caller);
858		// value and value transferred via call should be removed from the caller
859		assert_eq!(
860			T::Currency::balance(&instance.caller),
861			caller_funding::<T>() - value - deposit - code_deposit - mapping_deposit,
862		);
863		// contract should have received the value
864		assert_eq!(T::Currency::balance(&instance.account_id), before + value);
865		// contract should still exist
866		instance.info()?;
867
868		Ok(())
869	}
870
871	// `d`: with or without dust value to transfer
872	#[benchmark(pov_mode = Measured)]
873	fn eth_call(d: Linear<0, 1>) -> Result<(), BenchmarkError> {
874		let data = vec![42u8; 1024];
875		let instance =
876			Contract::<T>::with_caller(whitelisted_caller(), VmBinaryModule::dummy(), vec![])?;
877
878		// Use an `effective_gas_price` that is not a multiple of `T::NativeToEthRatio`
879		// to hit the code that charge the rounding error so that tx_cost == effective_gas_price *
880		// gas_used
881		let effective_gas_price = Pallet::<T>::evm_base_fee() + 1;
882		let value = Pallet::<T>::min_balance();
883		let dust = 42u32 * d;
884		let evm_value =
885			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
886
887		// need to pass the overdraw check
888		<T as Config>::FeeInfo::deposit_txfee(
889			<T as Config>::Currency::issue(caller_funding::<T>()),
890		);
891
892		let origin = Origin::EthTransaction(instance.caller.clone());
893		let before = Pallet::<T>::evm_balance(&instance.address);
894
895		#[extrinsic_call]
896		_(
897			origin,
898			instance.address,
899			evm_value,
900			Weight::MAX,
901			U256::MAX,
902			data,
903			TransactionSigned::default().signed_payload(),
904			effective_gas_price,
905			0,
906			vec![],
907		);
908
909		// contract should have received the value
910		assert_eq!(Pallet::<T>::evm_balance(&instance.address), before + evm_value);
911		// contract should still exist
912		instance.info()?;
913
914		Ok(())
915	}
916
917	// `c`: Size of the RLP encoded Ethereum transaction in bytes.
918	#[benchmark(pov_mode = Measured)]
919	fn eth_substrate_call(c: Linear<0, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
920		let caller = whitelisted_caller();
921		T::Currency::set_balance(&caller, caller_funding::<T>());
922		let origin = Origin::EthTransaction(caller);
923		let dispatchable = frame_system::Call::remark { remark: vec![] }.into();
924		#[extrinsic_call]
925		_(origin, Box::new(dispatchable), vec![42u8; c as usize]);
926		Ok(())
927	}
928
929	// This constructs a contract that is maximal expensive to instrument.
930	// It creates a maximum number of metering blocks per byte.
931	// `c`: Size of the code in bytes.
932	#[benchmark(pov_mode = Measured)]
933	fn upload_code(c: Linear<0, { 100 * 1024 }>) {
934		let caller = whitelisted_caller();
935		let pallet_account = whitelisted_pallet_account::<T>();
936		T::Currency::set_balance(&caller, caller_funding::<T>());
937		let VmBinaryModule { code, hash, .. } = VmBinaryModule::sized(c);
938		let origin = RawOrigin::Signed(caller.clone());
939		let storage_deposit = default_deposit_limit::<T>();
940		#[extrinsic_call]
941		_(origin, code, storage_deposit);
942		// uploading the code reserves some balance in the pallet's account
943		assert!(T::Currency::total_balance_on_hold(&pallet_account) > 0u32.into());
944		assert!(<Contract<T>>::code_exists(&hash));
945	}
946
947	// Removing code does not depend on the size of the contract because all the information
948	// needed to verify the removal claim (refcount, owner) is stored in a separate storage
949	// item (`CodeInfoOf`).
950	#[benchmark(pov_mode = Measured)]
951	fn remove_code() -> Result<(), BenchmarkError> {
952		let caller = whitelisted_caller();
953		let pallet_account = whitelisted_pallet_account::<T>();
954		T::Currency::set_balance(&caller, caller_funding::<T>());
955		let VmBinaryModule { code, hash, .. } = VmBinaryModule::dummy();
956		let origin = RawOrigin::Signed(caller.clone());
957		let storage_deposit = default_deposit_limit::<T>();
958		let uploaded =
959			<Contracts<T>>::bare_upload_code(origin.clone().into(), code, storage_deposit)?;
960		assert_eq!(uploaded.code_hash, hash);
961		assert_eq!(uploaded.deposit, T::Currency::total_balance_on_hold(&pallet_account));
962		assert!(<Contract<T>>::code_exists(&hash));
963		#[extrinsic_call]
964		_(origin, hash);
965		// removing the code should have unreserved the deposit
966		assert_eq!(T::Currency::total_balance_on_hold(&pallet_account), 0u32.into());
967		assert!(<Contract<T>>::code_removed(&hash));
968		Ok(())
969	}
970
971	#[benchmark(pov_mode = Measured)]
972	fn set_code() -> Result<(), BenchmarkError> {
973		let instance =
974			<Contract<T>>::with_caller(whitelisted_caller(), VmBinaryModule::dummy(), vec![])?;
975		// we just add some bytes so that the code hash is different
976		let VmBinaryModule { code, .. } = VmBinaryModule::dummy_unique(128);
977		let origin = RawOrigin::Signed(instance.caller.clone());
978		let storage_deposit = default_deposit_limit::<T>();
979		let hash =
980			<Contracts<T>>::bare_upload_code(origin.into(), code, storage_deposit)?.code_hash;
981		assert_ne!(instance.info()?.code_hash, hash);
982		#[extrinsic_call]
983		_(RawOrigin::Root, instance.address, hash);
984		assert_eq!(instance.info()?.code_hash, hash);
985		Ok(())
986	}
987
988	#[benchmark(pov_mode = Measured)]
989	fn map_account() {
990		let caller = whitelisted_caller();
991		T::Currency::set_balance(&caller, caller_funding::<T>());
992		let origin = RawOrigin::Signed(caller.clone());
993		if T::AddressMapper::is_mapped(&caller) {
994			T::AddressMapper::unmap(&caller).unwrap();
995		}
996		assert!(!T::AddressMapper::is_mapped(&caller));
997		#[extrinsic_call]
998		_(origin);
999		assert!(T::AddressMapper::is_mapped(&caller));
1000	}
1001
1002	#[benchmark(pov_mode = Measured)]
1003	fn unmap_account() {
1004		let caller = whitelisted_caller();
1005		T::Currency::set_balance(&caller, caller_funding::<T>());
1006		let origin = RawOrigin::Signed(caller.clone());
1007		if !T::AddressMapper::is_mapped(&caller) {
1008			T::AddressMapper::map(&caller).unwrap();
1009		}
1010		assert!(T::AddressMapper::is_mapped(&caller));
1011		#[extrinsic_call]
1012		_(origin);
1013		assert!(!T::AddressMapper::is_mapped(&caller));
1014	}
1015
1016	/// Worst case: every input account is not eth-derived, not yet mapped, and
1017	/// already carries an [`HoldReason::AddressMapping`] hold. The per-account
1018	/// loop body in `batch_map_accounts` then both inserts the [`OriginalAccount`]
1019	/// entry via `map_no_deposit_unchecked` *and* releases the existing hold.
1020	#[benchmark(pov_mode = Measured)]
1021	fn batch_map_accounts(a: Linear<0, 1024>) -> Result<(), BenchmarkError> {
1022		use frame_benchmarking::v2::account;
1023
1024		let caller: T::AccountId = whitelisted_caller();
1025		T::Currency::set_balance(&caller, caller_funding::<T>());
1026
1027		// Matches the deposit that `AccountId32Mapper::map` would normally take.
1028		let deposit = T::DepositPerByte::get()
1029			.saturating_mul(52u32.into())
1030			.saturating_add(T::DepositPerItem::get());
1031
1032		let mut accounts = Vec::with_capacity(a as usize);
1033		for i in 0..a {
1034			let account_id: T::AccountId = account("to_map", i, 0);
1035			T::Currency::set_balance(&account_id, caller_funding::<T>());
1036			T::Currency::hold(&HoldReason::AddressMapping.into(), &account_id, deposit)?;
1037			accounts.push(account_id);
1038		}
1039
1040		#[extrinsic_call]
1041		_(RawOrigin::Signed(caller), accounts.clone());
1042
1043		for account_id in &accounts {
1044			assert!(T::AddressMapper::is_mapped(account_id));
1045			assert_eq!(
1046				T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), account_id),
1047				0u32.into(),
1048			);
1049		}
1050
1051		Ok(())
1052	}
1053
1054	#[benchmark(pov_mode = Measured)]
1055	fn dispatch_as_fallback_account() {
1056		let caller = whitelisted_caller();
1057		T::Currency::set_balance(&caller, caller_funding::<T>());
1058		let origin = RawOrigin::Signed(caller.clone());
1059		let dispatchable = frame_system::Call::remark { remark: vec![] }.into();
1060		#[extrinsic_call]
1061		_(origin, Box::new(dispatchable));
1062	}
1063
1064	#[benchmark(pov_mode = Measured)]
1065	fn noop_host_fn(r: Linear<0, API_BENCHMARK_RUNS>) {
1066		let mut setup = CallSetup::<T>::new(VmBinaryModule::noop());
1067		let (mut ext, module) = setup.ext();
1068		let prepared = CallSetup::<T>::prepare_call(&mut ext, module, r.encode(), 0);
1069		#[block]
1070		{
1071			prepared.call().unwrap();
1072		}
1073	}
1074
1075	#[benchmark(pov_mode = Measured)]
1076	fn seal_caller() {
1077		let len = H160::len_bytes();
1078		build_runtime!(runtime, memory: [vec![0u8; len as _], ]);
1079
1080		let result;
1081		#[block]
1082		{
1083			result = runtime.bench_caller(memory.as_mut_slice(), 0);
1084		}
1085
1086		assert_ok!(result);
1087		assert_eq!(
1088			<H160 as Decode>::decode(&mut &memory[..]).unwrap(),
1089			T::AddressMapper::to_address(&runtime.ext().caller().account_id().unwrap())
1090		);
1091	}
1092
1093	#[benchmark(pov_mode = Measured)]
1094	fn seal_origin() {
1095		let len = H160::len_bytes();
1096		build_runtime!(runtime, memory: [vec![0u8; len as _], ]);
1097
1098		let result;
1099		#[block]
1100		{
1101			result = runtime.bench_origin(memory.as_mut_slice(), 0);
1102		}
1103
1104		assert_ok!(result);
1105		assert_eq!(
1106			<H160 as Decode>::decode(&mut &memory[..]).unwrap(),
1107			T::AddressMapper::to_address(&runtime.ext().origin().account_id().unwrap())
1108		);
1109	}
1110
1111	#[benchmark(pov_mode = Measured)]
1112	fn to_account_id() {
1113		// use a mapped address for the benchmark, to ensure that we bench the worst
1114		// case (and not the fallback case).
1115		let account_id = account("precompile_to_account_id", 0, 0);
1116		let address = {
1117			T::Currency::set_balance(&account_id, caller_funding::<T>());
1118			if !T::AddressMapper::is_mapped(&account_id) {
1119				T::AddressMapper::map(&account_id).unwrap();
1120			}
1121			T::AddressMapper::to_address(&account_id)
1122		};
1123
1124		let input_bytes = ISystem::ISystemCalls::toAccountId(ISystem::toAccountIdCall {
1125			input: address.0.into(),
1126		})
1127		.abi_encode();
1128
1129		let mut call_setup = CallSetup::<T>::default();
1130		let (mut ext, _) = call_setup.ext();
1131
1132		let result;
1133		#[block]
1134		{
1135			result = run_builtin_precompile(
1136				&mut ext,
1137				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1138				input_bytes,
1139			);
1140		}
1141		let raw_data = result.unwrap().data;
1142		let data = Bytes::abi_decode(&raw_data).expect("decoding failed");
1143		assert_ne!(
1144			data.0.as_ref()[20..32],
1145			[0xEE; 12],
1146			"fallback suffix found where none should be"
1147		);
1148		assert_eq!(T::AccountId::decode(&mut data.as_ref()), Ok(account_id),);
1149	}
1150
1151	#[benchmark(pov_mode = Measured)]
1152	fn seal_code_hash() {
1153		let contract = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
1154		let len = <sp_core::H256 as MaxEncodedLen>::max_encoded_len() as u32;
1155		build_runtime!(runtime, memory: [vec![0u8; len as _], contract.account_id.encode(), ]);
1156
1157		let result;
1158		#[block]
1159		{
1160			result = runtime.bench_code_hash(memory.as_mut_slice(), len, 0);
1161		}
1162
1163		assert_ok!(result);
1164		assert_eq!(
1165			<sp_core::H256 as Decode>::decode(&mut &memory[..]).unwrap(),
1166			contract.info().unwrap().code_hash
1167		);
1168	}
1169
1170	#[benchmark(pov_mode = Measured)]
1171	fn own_code_hash() {
1172		let input_bytes =
1173			ISystem::ISystemCalls::ownCodeHash(ISystem::ownCodeHashCall {}).abi_encode();
1174		let mut call_setup = CallSetup::<T>::default();
1175		let contract_acc = call_setup.contract().account_id.clone();
1176		let caller = call_setup.contract().address;
1177		call_setup.set_origin(ExecOrigin::from_account_id(contract_acc));
1178		let (mut ext, _) = call_setup.ext();
1179
1180		let result;
1181		#[block]
1182		{
1183			result = run_builtin_precompile(
1184				&mut ext,
1185				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1186				input_bytes,
1187			);
1188		}
1189		assert!(result.is_ok());
1190		let caller_code_hash = ext.code_hash(&caller);
1191		assert_eq!(caller_code_hash.0.to_vec(), result.unwrap().data);
1192	}
1193
1194	#[benchmark(pov_mode = Measured)]
1195	fn seal_code_size() {
1196		let contract = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
1197		build_runtime!(runtime, memory: [contract.address.encode(),]);
1198
1199		let result;
1200		#[block]
1201		{
1202			result = runtime.bench_code_size(memory.as_mut_slice(), 0);
1203		}
1204
1205		assert_eq!(result.unwrap(), VmBinaryModule::dummy().code.len() as u64);
1206	}
1207
1208	#[benchmark(pov_mode = Measured)]
1209	fn caller_is_origin() {
1210		let input_bytes =
1211			ISystem::ISystemCalls::callerIsOrigin(ISystem::callerIsOriginCall {}).abi_encode();
1212
1213		let mut call_setup = CallSetup::<T>::default();
1214		let (mut ext, _) = call_setup.ext();
1215
1216		let result;
1217		#[block]
1218		{
1219			result = run_builtin_precompile(
1220				&mut ext,
1221				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1222				input_bytes,
1223			);
1224		}
1225		let raw_data = result.unwrap().data;
1226		let is_origin = Bool::abi_decode(&raw_data[..]).expect("decoding failed");
1227		assert!(is_origin);
1228	}
1229
1230	#[benchmark(pov_mode = Measured)]
1231	fn caller_is_root() {
1232		let input_bytes =
1233			ISystem::ISystemCalls::callerIsRoot(ISystem::callerIsRootCall {}).abi_encode();
1234
1235		let mut setup = CallSetup::<T>::default();
1236		setup.set_origin(ExecOrigin::Root);
1237		let (mut ext, _) = setup.ext();
1238
1239		let result;
1240		#[block]
1241		{
1242			result = run_builtin_precompile(
1243				&mut ext,
1244				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1245				input_bytes,
1246			);
1247		}
1248		let raw_data = result.unwrap().data;
1249		let is_root = Bool::abi_decode(&raw_data).expect("decoding failed");
1250		assert!(is_root);
1251	}
1252
1253	#[benchmark(pov_mode = Measured)]
1254	fn origin_is_root() {
1255		let input_bytes =
1256			ISystem::ISystemCalls::originIsRoot(ISystem::originIsRootCall {}).abi_encode();
1257
1258		let mut setup = CallSetup::<T>::default();
1259		setup.set_origin(ExecOrigin::Root);
1260		let (mut ext, _) = setup.ext();
1261
1262		let result;
1263		#[block]
1264		{
1265			result = run_builtin_precompile(
1266				&mut ext,
1267				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1268				input_bytes,
1269			);
1270		}
1271		let raw_data = result.unwrap().data;
1272		let is_root = Bool::abi_decode(&raw_data).expect("decoding failed");
1273		assert!(is_root);
1274	}
1275
1276	#[benchmark(pov_mode = Measured)]
1277	fn seal_address() {
1278		let len = H160::len_bytes();
1279		build_runtime!(runtime, memory: [vec![0u8; len as _], ]);
1280
1281		let result;
1282		#[block]
1283		{
1284			result = runtime.bench_address(memory.as_mut_slice(), 0);
1285		}
1286		assert_ok!(result);
1287		assert_eq!(<H160 as Decode>::decode(&mut &memory[..]).unwrap(), runtime.ext().address());
1288	}
1289
1290	#[benchmark(pov_mode = Measured)]
1291	fn weight_left() {
1292		let input_bytes =
1293			ISystem::ISystemCalls::weightLeft(ISystem::weightLeftCall {}).abi_encode();
1294
1295		let mut call_setup = CallSetup::<T>::default();
1296		let (mut ext, _) = call_setup.ext();
1297
1298		let weight_left_before = ext.frame_meter().weight_left().unwrap();
1299		let result;
1300		#[block]
1301		{
1302			result = run_builtin_precompile(
1303				&mut ext,
1304				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1305				input_bytes,
1306			);
1307		}
1308		let weight_left_after = ext.frame_meter().weight_left().unwrap();
1309		assert_ne!(weight_left_after.ref_time(), 0);
1310		assert!(weight_left_before.ref_time() > weight_left_after.ref_time());
1311
1312		let raw_data = result.unwrap().data;
1313		type MyTy = (Uint<64>, Uint<64>);
1314		let foo = MyTy::abi_decode(&raw_data[..]).unwrap();
1315		assert_eq!(weight_left_after.ref_time(), foo.0);
1316	}
1317
1318	#[benchmark(pov_mode = Measured)]
1319	fn seal_ref_time_left() {
1320		build_runtime!(runtime, memory: [vec![], ]);
1321
1322		let result;
1323		#[block]
1324		{
1325			result = runtime.bench_ref_time_left(memory.as_mut_slice());
1326		}
1327		assert_eq!(result.unwrap(), runtime.ext().gas_left());
1328	}
1329
1330	#[benchmark(pov_mode = Measured)]
1331	fn seal_balance() {
1332		build_runtime!(runtime, contract, memory: [[0u8;32], ]);
1333		contract.set_balance(BalanceWithDust::new_unchecked::<T>(
1334			Pallet::<T>::min_balance() * 2u32.into(),
1335			42u32,
1336		));
1337
1338		let result;
1339		#[block]
1340		{
1341			result = runtime.bench_balance(memory.as_mut_slice(), 0);
1342		}
1343		assert_ok!(result);
1344		assert_eq!(
1345			U256::from_little_endian(&memory[..]),
1346			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(
1347				Pallet::<T>::min_balance(),
1348				42
1349			))
1350		);
1351	}
1352
1353	#[benchmark(pov_mode = Measured)]
1354	fn seal_balance_of() {
1355		let len = <sp_core::U256 as MaxEncodedLen>::max_encoded_len();
1356		let account = account::<T::AccountId>("target", 0, 0);
1357		<T as Config>::AddressMapper::map_no_deposit_unchecked(&account).unwrap();
1358
1359		let address = T::AddressMapper::to_address(&account);
1360		let balance = Pallet::<T>::min_balance() * 2u32.into();
1361		T::Currency::set_balance(&account, balance);
1362		AccountInfoOf::<T>::insert(&address, AccountInfo { dust: 42, ..Default::default() });
1363
1364		build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]);
1365
1366		let result;
1367		#[block]
1368		{
1369			result = runtime.bench_balance_of(memory.as_mut_slice(), len as u32, 0);
1370		}
1371
1372		assert_ok!(result);
1373		assert_eq!(
1374			U256::from_little_endian(&memory[..len]),
1375			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(
1376				Pallet::<T>::min_balance(),
1377				42
1378			))
1379		);
1380	}
1381
1382	#[benchmark(pov_mode = Measured)]
1383	fn seal_get_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) {
1384		let len = n as usize;
1385		let immutable_data = vec![1u8; len];
1386
1387		build_runtime!(runtime, contract, memory: [(len as u32).encode(), vec![0u8; len],]);
1388
1389		<ImmutableDataOf<T>>::insert::<_, BoundedVec<_, _>>(
1390			contract.address,
1391			immutable_data.clone().try_into().unwrap(),
1392		);
1393
1394		let result;
1395		#[block]
1396		{
1397			result = runtime.bench_get_immutable_data(memory.as_mut_slice(), 4, 0 as u32);
1398		}
1399
1400		assert_ok!(result);
1401		assert_eq!(&memory[0..4], (len as u32).encode());
1402		assert_eq!(&memory[4..len + 4], &immutable_data);
1403	}
1404
1405	#[benchmark(pov_mode = Measured)]
1406	fn seal_set_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) {
1407		let len = n as usize;
1408		let mut memory = vec![1u8; len];
1409		let mut setup = CallSetup::<T>::default();
1410		let input = setup.data();
1411		let (mut ext, _) = setup.ext();
1412		ext.override_export(crate::exec::ExportedFunction::Constructor);
1413
1414		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input);
1415
1416		let result;
1417		#[block]
1418		{
1419			result = runtime.bench_set_immutable_data(memory.as_mut_slice(), 0, n);
1420		}
1421
1422		assert_ok!(result);
1423		assert_eq!(&memory[..], &<ImmutableDataOf<T>>::get(setup.contract().address).unwrap()[..]);
1424	}
1425
1426	#[benchmark(pov_mode = Measured)]
1427	fn seal_value_transferred() {
1428		build_runtime!(runtime, memory: [[0u8;32], ]);
1429		let result;
1430		#[block]
1431		{
1432			result = runtime.bench_value_transferred(memory.as_mut_slice(), 0);
1433		}
1434		assert_ok!(result);
1435		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().value_transferred());
1436	}
1437
1438	#[benchmark(pov_mode = Measured)]
1439	fn minimum_balance() {
1440		let input_bytes =
1441			ISystem::ISystemCalls::minimumBalance(ISystem::minimumBalanceCall {}).abi_encode();
1442
1443		let mut call_setup = CallSetup::<T>::default();
1444		let (mut ext, _) = call_setup.ext();
1445
1446		let result;
1447		#[block]
1448		{
1449			result = run_builtin_precompile(
1450				&mut ext,
1451				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1452				input_bytes,
1453			);
1454		}
1455		let min: U256 = crate::Pallet::<T>::convert_native_to_evm(T::Currency::minimum_balance());
1456		let min =
1457			crate::precompiles::alloy::primitives::aliases::U256::abi_decode(&min.to_big_endian())
1458				.unwrap();
1459
1460		let raw_data = result.unwrap().data;
1461		let returned_min =
1462			crate::precompiles::alloy::primitives::aliases::U256::abi_decode(&raw_data)
1463				.expect("decoding failed");
1464		assert_eq!(returned_min, min);
1465	}
1466
1467	#[benchmark(pov_mode = Measured)]
1468	fn seal_return_data_size() {
1469		let mut setup = CallSetup::<T>::default();
1470		let (mut ext, _) = setup.ext();
1471		let mut runtime = pvm::Runtime::new(&mut ext, vec![]);
1472		let mut memory = memory!(vec![],);
1473		*runtime.ext().last_frame_output_mut() =
1474			ExecReturnValue { data: vec![42; 256], ..Default::default() };
1475		let result;
1476		#[block]
1477		{
1478			result = runtime.bench_return_data_size(memory.as_mut_slice());
1479		}
1480		assert_eq!(result.unwrap(), 256);
1481	}
1482
1483	#[benchmark(pov_mode = Measured)]
1484	fn seal_call_data_size() {
1485		let mut setup = CallSetup::<T>::default();
1486		let (mut ext, _) = setup.ext();
1487		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 128 as usize]);
1488		let mut memory = memory!(vec![0u8; 4],);
1489		let result;
1490		#[block]
1491		{
1492			result = runtime.bench_call_data_size(memory.as_mut_slice());
1493		}
1494		assert_eq!(result.unwrap(), 128);
1495	}
1496
1497	#[benchmark(pov_mode = Measured)]
1498	fn seal_gas_limit() {
1499		build_runtime!(runtime, memory: []);
1500		let result;
1501		#[block]
1502		{
1503			result = runtime.bench_gas_limit(&mut memory);
1504		}
1505		assert_eq!(U256::from(result.unwrap()), <Pallet<T>>::evm_block_gas_limit());
1506	}
1507
1508	#[benchmark(pov_mode = Measured)]
1509	fn seal_gas_price() {
1510		build_runtime!(runtime, memory: []);
1511		let result;
1512		#[block]
1513		{
1514			result = runtime.bench_gas_price(memory.as_mut_slice());
1515		}
1516		assert_eq!(U256::from(result.unwrap()), <Pallet<T>>::evm_base_fee());
1517	}
1518
1519	#[benchmark(pov_mode = Measured)]
1520	fn seal_base_fee() {
1521		build_runtime!(runtime, memory: [[1u8;32], ]);
1522		let result;
1523		#[block]
1524		{
1525			result = runtime.bench_base_fee(memory.as_mut_slice(), 0);
1526		}
1527		assert_ok!(result);
1528		assert_eq!(U256::from_little_endian(&memory[..]), <crate::Pallet<T>>::evm_base_fee());
1529	}
1530
1531	#[benchmark(pov_mode = Measured)]
1532	fn seal_block_number() {
1533		build_runtime!(runtime, memory: [[0u8;32], ]);
1534		let result;
1535		#[block]
1536		{
1537			result = runtime.bench_block_number(memory.as_mut_slice(), 0);
1538		}
1539		assert_ok!(result);
1540		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().block_number());
1541	}
1542
1543	#[benchmark(pov_mode = Measured)]
1544	fn seal_block_author() {
1545		build_runtime!(runtime, memory: [[123u8; 20], ]);
1546
1547		// The pre-runtime digest log is unbounded; usually around 3 items but it can vary.
1548		// To get safe benchmark results despite that, populate it with a bunch of random logs to
1549		// ensure iteration over many items (we just overestimate the cost of the API).
1550		for i in 0..16 {
1551			frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1552				[i, i, i, i],
1553				vec![i; 128],
1554			));
1555			frame_system::Pallet::<T>::deposit_log(DigestItem::Consensus(
1556				[i, i, i, i],
1557				vec![i; 128],
1558			));
1559			frame_system::Pallet::<T>::deposit_log(DigestItem::Seal([i, i, i, i], vec![i; 128]));
1560			frame_system::Pallet::<T>::deposit_log(DigestItem::Other(vec![i; 128]));
1561		}
1562
1563		// The content of the pre-runtime digest log depends on the configured consensus.
1564		// However, mismatching logs are simply ignored. Thus we construct fixtures which will
1565		// let the API to return a value in both BABE and AURA consensus.
1566
1567		// Construct a `Digest` log fixture returning some value in BABE
1568		let primary_pre_digest = vec![0; <PrimaryPreDigest as MaxEncodedLen>::max_encoded_len()];
1569		let pre_digest =
1570			PreDigest::Primary(PrimaryPreDigest::decode(&mut &primary_pre_digest[..]).unwrap());
1571		frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1572			BABE_ENGINE_ID,
1573			pre_digest.encode(),
1574		));
1575		frame_system::Pallet::<T>::deposit_log(DigestItem::Seal(
1576			BABE_ENGINE_ID,
1577			pre_digest.encode(),
1578		));
1579
1580		// Construct a `Digest` log fixture returning some value in AURA
1581		let slot = Slot::default();
1582		frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1583			AURA_ENGINE_ID,
1584			slot.encode(),
1585		));
1586		frame_system::Pallet::<T>::deposit_log(DigestItem::Seal(AURA_ENGINE_ID, slot.encode()));
1587
1588		let result;
1589		#[block]
1590		{
1591			result = runtime.bench_block_author(memory.as_mut_slice(), 0);
1592		}
1593		assert_ok!(result);
1594
1595		let block_author = runtime.ext().block_author();
1596		assert_eq!(&memory[..], block_author.as_bytes());
1597	}
1598
1599	#[benchmark(pov_mode = Measured)]
1600	fn seal_block_hash() {
1601		let mut memory = vec![0u8; 64];
1602		let mut setup = CallSetup::<T>::default();
1603		let input = setup.data();
1604		let (mut ext, _) = setup.ext();
1605		ext.set_block_number(BlockNumberFor::<T>::from(1u32));
1606
1607		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input);
1608
1609		let block_hash = H256::from([1; 32]);
1610
1611		// Store block hash in pallet-revive BlockHash mapping
1612		crate::BlockHash::<T>::insert(crate::BlockNumberFor::<T>::from(0u32), block_hash);
1613
1614		let result;
1615		#[block]
1616		{
1617			result = runtime.bench_block_hash(memory.as_mut_slice(), 32, 0);
1618		}
1619		assert_ok!(result);
1620		assert_eq!(&memory[..32], &block_hash.0);
1621	}
1622
1623	#[benchmark(pov_mode = Measured)]
1624	fn seal_now() {
1625		build_runtime!(runtime, memory: [[0u8;32], ]);
1626		let result;
1627		#[block]
1628		{
1629			result = runtime.bench_now(memory.as_mut_slice(), 0);
1630		}
1631		assert_ok!(result);
1632		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().now());
1633	}
1634
1635	#[benchmark(pov_mode = Measured)]
1636	fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) {
1637		let mut setup = CallSetup::<T>::default();
1638		let (mut ext, _) = setup.ext();
1639		let mut runtime = pvm::Runtime::new(&mut ext, vec![]);
1640		let mut memory = memory!(n.encode(), vec![0u8; n as usize],);
1641		let result;
1642		#[block]
1643		{
1644			result = runtime.write_sandbox_output(
1645				memory.as_mut_slice(),
1646				4,
1647				0,
1648				&vec![42u8; n as usize],
1649				false,
1650				|_| None,
1651			);
1652		}
1653		assert_ok!(result);
1654		assert_eq!(&memory[..4], &n.encode());
1655		assert_eq!(&memory[4..], &vec![42u8; n as usize]);
1656	}
1657
1658	#[benchmark(pov_mode = Measured)]
1659	fn seal_call_data_load() {
1660		let mut setup = CallSetup::<T>::default();
1661		let (mut ext, _) = setup.ext();
1662		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 32]);
1663		let mut memory = memory!(vec![0u8; 32],);
1664		let result;
1665		#[block]
1666		{
1667			result = runtime.bench_call_data_load(memory.as_mut_slice(), 0, 0);
1668		}
1669		assert_ok!(result);
1670		assert_eq!(&memory[..], &vec![42u8; 32]);
1671	}
1672
1673	#[benchmark(pov_mode = Measured)]
1674	fn seal_call_data_copy(n: Linear<0, { limits::code::BLOB_BYTES }>) {
1675		let mut setup = CallSetup::<T>::default();
1676		let (mut ext, _) = setup.ext();
1677		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; n as usize]);
1678		let mut memory = memory!(vec![0u8; n as usize],);
1679		let result;
1680		#[block]
1681		{
1682			result = runtime.bench_call_data_copy(memory.as_mut_slice(), 0, n, 0);
1683		}
1684		assert_ok!(result);
1685		assert_eq!(&memory[..], &vec![42u8; n as usize]);
1686	}
1687
1688	#[benchmark(pov_mode = Measured)]
1689	fn seal_return(n: Linear<0, { limits::CALLDATA_BYTES }>) {
1690		build_runtime!(runtime, memory: [n.to_le_bytes(), vec![42u8; n as usize], ]);
1691
1692		let result;
1693		#[block]
1694		{
1695			result = runtime.bench_seal_return(memory.as_mut_slice(), 0, 0, n);
1696		}
1697
1698		assert!(matches!(
1699			result,
1700			Err(crate::vm::pvm::TrapReason::Return(crate::vm::pvm::ReturnData { .. }))
1701		));
1702	}
1703
1704	/// Benchmark the ocst of terminating a contract.
1705	///
1706	/// `r`: whether the old code will be removed as a result of this operation. (1: yes, 0: no)
1707	#[benchmark(pov_mode = Measured)]
1708	fn seal_terminate(r: Linear<0, 1>) -> Result<(), BenchmarkError> {
1709		let delete_code = r == 1;
1710		let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
1711
1712		build_runtime!(runtime, instance, memory: [beneficiary.encode(),]);
1713		let code_hash = instance.info()?.code_hash;
1714
1715		// Increment the refcount of the code hash so that it does not get deleted
1716		if !delete_code {
1717			<CodeInfo<T>>::increment_refcount(code_hash).unwrap();
1718		}
1719
1720		let result;
1721		#[block]
1722		{
1723			result = runtime.bench_terminate(memory.as_mut_slice(), 0);
1724		}
1725
1726		assert!(matches!(result, Err(crate::vm::pvm::TrapReason::Termination)));
1727
1728		Ok(())
1729	}
1730
1731	/// The balance work of `System.terminate`: the storage deposit refund when it is called and
1732	/// the teardown at the end of the call stack.
1733	#[benchmark(pov_mode = Measured)]
1734	fn seal_terminate_logic() -> Result<(), BenchmarkError> {
1735		let caller = whitelisted_caller();
1736		let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
1737		T::AddressMapper::map_no_deposit_unchecked(&beneficiary)?;
1738
1739		build_runtime!(_runtime, instance, _memory: [vec![0u8; 0], ]);
1740		let code_hash = instance.info()?.code_hash;
1741
1742		assert!(PristineCode::<T>::get(code_hash).is_some());
1743
1744		T::Currency::set_balance(&instance.account_id, Pallet::<T>::min_balance() * 10u32.into());
1745
1746		let storage_deposit = T::Currency::balance_on_hold(
1747			&HoldReason::StorageDepositReserve.into(),
1748			&instance.account_id,
1749		);
1750		NativeDepositOf::<T>::insert(&instance.account_id, &caller, storage_deposit);
1751
1752		let mut transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit {
1753			weight_limit: Default::default(),
1754			deposit_limit: BalanceOf::<T>::max_value(),
1755		})
1756		.unwrap();
1757		let exec_config = ExecConfig::new_substrate_tx();
1758		let contract_account = &instance.account_id;
1759		let origin = &ExecOrigin::from_account_id(caller);
1760		let beneficiary_clone = beneficiary.clone();
1761		let only_if_same_tx = false;
1762
1763		let result;
1764		#[block]
1765		{
1766			result = crate::exec::bench_do_terminate::<T>(
1767				&mut transaction_meter,
1768				&exec_config,
1769				contract_account,
1770				&origin,
1771				beneficiary_clone,
1772				only_if_same_tx,
1773			);
1774		}
1775		result.unwrap();
1776
1777		// Check that the contract is removed
1778		assert!(PristineCode::<T>::get(code_hash).is_none());
1779
1780		// Check that the balance has been transferred away
1781		let balance = <T as Config>::Currency::total_balance(&instance.account_id);
1782		assert_eq!(balance, 0u32.into());
1783
1784		// Check that the beneficiary received the balance
1785		let balance = <T as Config>::Currency::balance(&beneficiary);
1786		assert_eq!(balance, Pallet::<T>::min_balance() + Pallet::<T>::min_balance() * 9u32.into());
1787
1788		Ok(())
1789	}
1790
1791	// Benchmark the overhead that topics generate.
1792	// `t`: Number of topics
1793	// `n`: Size of event payload in bytes
1794	#[benchmark(pov_mode = Measured)]
1795	fn seal_deposit_event(
1796		t: Linear<0, { limits::NUM_EVENT_TOPICS as u32 }>,
1797		n: Linear<0, { limits::EVENT_BYTES }>,
1798	) {
1799		let num_topic = t as u32;
1800		let topics = (0..t).map(|i| H256::repeat_byte(i as u8)).collect::<Vec<_>>();
1801		let topics_data =
1802			topics.iter().flat_map(|hash| hash.as_bytes().to_vec()).collect::<Vec<u8>>();
1803		let data = vec![42u8; n as _];
1804		build_runtime!(runtime, instance, memory: [ topics_data, data, ]);
1805
1806		// Inside an ethereum transaction, the worst case: the log is also captured into the open
1807		// receipt, which encodes it, accrues the bloom and writes what the transaction has
1808		// committed so far to storage.
1809		let result;
1810		#[block]
1811		{
1812			result = block_storage::bench_with_ethereum_context(|| {
1813				runtime.bench_deposit_event(
1814					memory.as_mut_slice(),
1815					0, // topics_ptr
1816					num_topic,
1817					topics_data.len() as u32, // data_ptr
1818					n,                        // data_len
1819				)
1820			});
1821		}
1822		assert_ok!(result);
1823
1824		let events = System::<T>::events();
1825		let record = &events[events.len() - 1];
1826
1827		assert_eq!(
1828			record.event,
1829			crate::Event::ContractEmitted { contract: instance.address, data, topics }.into(),
1830		);
1831	}
1832
1833	enum TrieFill {
1834		Empty,
1835		Full,
1836	}
1837
1838	enum SlotAccess {
1839		Cold,
1840		Hot,
1841	}
1842
1843	fn build_storage_contract<T: Config>(
1844		op: StorageOp,
1845		fill: TrieFill,
1846	) -> Result<(ContractInfo<T>, Vec<u8>, Vec<u8>), BenchmarkError> {
1847		let key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1848		let value = vec![1u8; limits::STORAGE_BYTES as usize];
1849		let initial_value = match op {
1850			StorageOp::Read => value.clone(),
1851			StorageOp::Write => vec![42u8; limits::STORAGE_BYTES as usize],
1852		};
1853
1854		let instance = match fill {
1855			TrieFill::Full => {
1856				Contract::<T>::with_unbalanced_storage_trie(VmBinaryModule::dummy(), &key)?
1857			},
1858			TrieFill::Empty => Contract::<T>::new(VmBinaryModule::dummy(), vec![])?,
1859		};
1860		let info = instance.info()?;
1861		info.bench_write_raw(&key, Some(initial_value), false)
1862			.map_err(|_| "Failed to write to storage during setup.")?;
1863		Ok((info, key, value))
1864	}
1865
1866	enum StorageCall {
1867		Clear,
1868		Contains,
1869		Take,
1870	}
1871
1872	fn setup_precompile_bench<T: Config>(
1873		op: StorageCall,
1874		key_byte: u8,
1875		access: SlotAccess,
1876	) -> Result<(CallSetup<T>, Key, Vec<u8>), BenchmarkError> {
1877		let max_key_len = limits::STORAGE_KEY_BYTES;
1878		let key = Key::try_from_var(vec![key_byte; max_key_len as usize])
1879			.map_err(|_| "Key has wrong length")?;
1880		let raw_key = vec![key_byte; max_key_len as usize].into();
1881		let input_bytes = match op {
1882			StorageCall::Clear => {
1883				IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
1884					flags: StorageFlags::empty().bits(),
1885					key: raw_key,
1886					isFixedKey: false,
1887				})
1888			},
1889			StorageCall::Contains => {
1890				IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
1891					flags: StorageFlags::empty().bits(),
1892					key: raw_key,
1893					isFixedKey: false,
1894				})
1895			},
1896			StorageCall::Take => IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
1897				flags: StorageFlags::empty().bits(),
1898				key: raw_key,
1899				isFixedKey: false,
1900			}),
1901		}
1902		.abi_encode();
1903
1904		let call_setup = CallSetup::<T>::default();
1905		if matches!(access, SlotAccess::Hot) {
1906			let info = call_setup.contract().info()?;
1907			frame_benchmarking::add_to_whitelist_child(
1908				info.child_trie_info().storage_key().to_vec(),
1909				key.hash(),
1910			);
1911		}
1912		Ok((call_setup, key, input_bytes))
1913	}
1914
1915	#[benchmark(skip_meta, pov_mode = Measured)]
1916	fn get_storage_empty() -> Result<(), BenchmarkError> {
1917		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Empty)?;
1918		let child_trie_info = info.child_trie_info();
1919
1920		let result;
1921		#[block]
1922		{
1923			result = child::get_raw(&child_trie_info, &key);
1924		}
1925
1926		assert_eq!(result, Some(value));
1927		Ok(())
1928	}
1929
1930	#[benchmark(skip_meta, pov_mode = Measured)]
1931	fn get_storage_full() -> Result<(), BenchmarkError> {
1932		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Full)?;
1933		let child_trie_info = info.child_trie_info();
1934
1935		let result;
1936		#[block]
1937		{
1938			result = child::get_raw(&child_trie_info, &key);
1939		}
1940
1941		assert_eq!(result, Some(value));
1942		Ok(())
1943	}
1944
1945	#[benchmark(skip_meta, pov_mode = Measured)]
1946	fn set_storage_empty() -> Result<(), BenchmarkError> {
1947		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Empty)?;
1948
1949		let val = Some(value.clone());
1950		let result;
1951		#[block]
1952		{
1953			result = info.bench_write_raw(&key, val, true);
1954		}
1955
1956		assert_ok!(result);
1957		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1958		Ok(())
1959	}
1960
1961	#[benchmark(skip_meta, pov_mode = Measured)]
1962	fn set_storage_full() -> Result<(), BenchmarkError> {
1963		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Full)?;
1964
1965		let val = Some(value.clone());
1966		let result;
1967		#[block]
1968		{
1969			result = info.bench_write_raw(&key, val, true);
1970		}
1971
1972		assert_ok!(result);
1973		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1974		Ok(())
1975	}
1976
1977	/// Worst-case lookup keys: differing only at the tail, comparisons walk the whole key.
1978	fn shared_prefix_keys(count: usize, suffix_of: impl Fn(u64) -> u64) -> Vec<Vec<u8>> {
1979		(0..count as u64)
1980			.map(|i| {
1981				let mut key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1982				key[limits::STORAGE_KEY_BYTES as usize - 8..]
1983					.copy_from_slice(&suffix_of(i).to_be_bytes());
1984				key
1985			})
1986			.collect()
1987	}
1988
1989	/// Probed keys must exist, or reads measure a proof-of-absence walk instead.
1990	/// Whitelisting keeps them out of the DB counters and pre-warms the cache.
1991	fn setup_stored_keys<T: Config>(
1992		count: usize,
1993		value_byte: u8,
1994		suffix_of: impl Fn(u64) -> u64,
1995	) -> Result<(ContractInfo<T>, Vec<Vec<u8>>), BenchmarkError> {
1996		let instance = Contract::<T>::new(VmBinaryModule::dummy(), vec![])?;
1997		let info = instance.info()?;
1998		let child_trie_info = info.child_trie_info();
1999		let value = vec![value_byte; limits::STORAGE_BYTES as usize];
2000		let stored_keys = shared_prefix_keys(count, suffix_of);
2001		for key in &stored_keys {
2002			info.bench_write_raw(key, Some(value.clone()), false)
2003				.map_err(|_| "Failed to write to storage during setup.")?;
2004			frame_benchmarking::add_to_whitelist_child(
2005				child_trie_info.storage_key().to_vec(),
2006				key.clone(),
2007			);
2008		}
2009		Ok((info, stored_keys))
2010	}
2011
2012	#[benchmark(skip_meta, pov_mode = Measured)]
2013	fn overlay_probe_full(
2014		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
2015	) -> Result<(), BenchmarkError> {
2016		let value_byte = 42;
2017		// One stored key per probe; odd suffixes, so the even fill never overwrites them.
2018		let (info, stored_keys) =
2019			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
2020		let child_trie_info = info.child_trie_info();
2021		// The block's PoV budget bounds the overlay entries like the access list cap.
2022		let fill_keys = shared_prefix_keys(MAX_ACCESS_LIST_ENTRIES, |i| (i + 1) * 2);
2023
2024		let mut result = None;
2025		#[block]
2026		{
2027			// The benchmark framework drains the overlay right before the block, so fill here.
2028			for key in &fill_keys {
2029				child::put_raw(&child_trie_info, key, &[0u8]);
2030			}
2031			for i in 0..n {
2032				let index = i as usize % stored_keys.len();
2033				result = child::get_raw(&child_trie_info, &stored_keys[index]);
2034			}
2035		}
2036
2037		if n > 0 {
2038			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
2039			assert_eq!(result, Some(expected), "the stored value must be read back");
2040		}
2041		Ok(())
2042	}
2043
2044	#[benchmark(skip_meta, pov_mode = Measured)]
2045	fn overlay_probe_empty(
2046		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
2047	) -> Result<(), BenchmarkError> {
2048		let value_byte = 42;
2049		// Reads must cost the same as in `overlay_probe_full`, so their shared cost cancels.
2050		let (info, stored_keys) =
2051			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
2052		let child_trie_info = info.child_trie_info();
2053
2054		let mut result = None;
2055		#[block]
2056		{
2057			for i in 0..n {
2058				let index = i as usize % stored_keys.len();
2059				result = child::get_raw(&child_trie_info, &stored_keys[index]);
2060			}
2061		}
2062
2063		if n > 0 {
2064			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
2065			assert_eq!(result, Some(expected), "the stored value must be read back");
2066		}
2067		Ok(())
2068	}
2069
2070	// n: new byte size
2071	// o: old byte size
2072	#[benchmark(skip_meta, pov_mode = Measured)]
2073	fn seal_set_storage(
2074		n: Linear<0, { limits::STORAGE_BYTES }>,
2075		o: Linear<0, { limits::STORAGE_BYTES }>,
2076	) -> Result<(), BenchmarkError> {
2077		let max_key_len = limits::STORAGE_KEY_BYTES;
2078		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2079			.map_err(|_| "Key has wrong length")?;
2080		let value = vec![1u8; n as usize];
2081
2082		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
2083		let info = instance.info()?;
2084
2085		info.write(&key, Some(vec![42u8; o as usize]), None, false)
2086			.map_err(|_| "Failed to write to storage during setup.")?;
2087
2088		let result;
2089		#[block]
2090		{
2091			result = runtime.bench_set_storage(
2092				memory.as_mut_slice(),
2093				StorageFlags::empty().bits(),
2094				0,           // key_ptr
2095				max_key_len, // key_len
2096				max_key_len, // value_ptr
2097				n,           // value_len
2098			);
2099		}
2100
2101		assert_ok!(result);
2102		assert_eq!(info.read(&key).unwrap(), value);
2103		Ok(())
2104	}
2105
2106	#[benchmark(skip_meta, pov_mode = Measured)]
2107	fn seal_set_storage_hot(
2108		n: Linear<0, { limits::STORAGE_BYTES }>,
2109		o: Linear<0, { limits::STORAGE_BYTES }>,
2110	) -> Result<(), BenchmarkError> {
2111		let max_key_len = limits::STORAGE_KEY_BYTES;
2112		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2113			.map_err(|_| "Key has wrong length")?;
2114		let value = vec![1u8; n as usize];
2115
2116		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
2117		let info = instance.info()?;
2118
2119		info.write(&key, Some(vec![42u8; o as usize]), None, false)
2120			.map_err(|_| "Failed to write to storage during setup.")?;
2121
2122		frame_benchmarking::add_to_whitelist_child(
2123			info.child_trie_info().storage_key().to_vec(),
2124			key.hash(),
2125		);
2126
2127		// Add the key to access list so the op's touch is hot.
2128		runtime.ext().touch_storage_access(&key, StorageOp::Write);
2129
2130		let result;
2131		#[block]
2132		{
2133			result = runtime.bench_set_storage(
2134				memory.as_mut_slice(),
2135				StorageFlags::empty().bits(),
2136				0,           // key_ptr
2137				max_key_len, // key_len
2138				max_key_len, // value_ptr
2139				n,           // value_len
2140			);
2141		}
2142
2143		assert_ok!(result);
2144		assert_eq!(info.read(&key).unwrap(), value);
2145		Ok(())
2146	}
2147
2148	#[benchmark(skip_meta, pov_mode = Measured)]
2149	fn clear_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2150		let key_byte = 0;
2151		let (mut call_setup, key, input_bytes) =
2152			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Cold)?;
2153		let (mut ext, _) = call_setup.ext();
2154		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2155			.map_err(|_| "Failed to write to storage during setup.")?;
2156
2157		let result;
2158		#[block]
2159		{
2160			result = run_builtin_precompile(
2161				&mut ext,
2162				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2163				input_bytes,
2164			);
2165		}
2166		assert_ok!(result);
2167		assert!(ext.get_storage(&key).is_none());
2168
2169		Ok(())
2170	}
2171
2172	#[benchmark(skip_meta, pov_mode = Measured)]
2173	fn clear_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2174		let key_byte = 0;
2175		let (mut call_setup, key, input_bytes) =
2176			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Hot)?;
2177		let (mut ext, _) = call_setup.ext();
2178		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2179			.map_err(|_| "Failed to write to storage during setup.")?;
2180
2181		ext.touch_storage_access(&key, StorageOp::Write);
2182
2183		let result;
2184		#[block]
2185		{
2186			result = run_builtin_precompile(
2187				&mut ext,
2188				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2189				input_bytes,
2190			);
2191		}
2192		assert_ok!(result);
2193		assert!(ext.get_storage(&key).is_none());
2194
2195		Ok(())
2196	}
2197
2198	#[benchmark(skip_meta, pov_mode = Measured)]
2199	fn seal_get_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2200		let max_key_len = limits::STORAGE_KEY_BYTES;
2201		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2202			.map_err(|_| "Key has wrong length")?;
2203		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2204		let info = instance.info()?;
2205
2206		info.write(&key, Some(vec![42u8; n as usize]), None, false)
2207			.map_err(|_| "Failed to write to storage during setup.")?;
2208
2209		let out_ptr = max_key_len + 4;
2210		let result;
2211		#[block]
2212		{
2213			result = runtime.bench_get_storage(
2214				memory.as_mut_slice(),
2215				StorageFlags::empty().bits(),
2216				0,           // key_ptr
2217				max_key_len, // key_len
2218				out_ptr,     // out_ptr
2219				max_key_len, // out_len_ptr
2220			);
2221		}
2222
2223		assert_ok!(result);
2224		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
2225		Ok(())
2226	}
2227
2228	#[benchmark(skip_meta, pov_mode = Measured)]
2229	fn seal_get_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2230		let max_key_len = limits::STORAGE_KEY_BYTES;
2231		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2232			.map_err(|_| "Key has wrong length")?;
2233		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2234		let info = instance.info()?;
2235
2236		info.write(&key, Some(vec![42u8; n as usize]), None, false)
2237			.map_err(|_| "Failed to write to storage during setup.")?;
2238
2239		frame_benchmarking::add_to_whitelist_child(
2240			info.child_trie_info().storage_key().to_vec(),
2241			key.hash(),
2242		);
2243
2244		runtime.ext().touch_storage_access(&key, StorageOp::Read);
2245
2246		let out_ptr = max_key_len + 4;
2247		let result;
2248		#[block]
2249		{
2250			result = runtime.bench_get_storage(
2251				memory.as_mut_slice(),
2252				StorageFlags::empty().bits(),
2253				0,           // key_ptr
2254				max_key_len, // key_len
2255				out_ptr,     // out_ptr
2256				max_key_len, // out_len_ptr
2257			);
2258		}
2259
2260		assert_ok!(result);
2261		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
2262		Ok(())
2263	}
2264
2265	#[benchmark(skip_meta, pov_mode = Measured)]
2266	fn contains_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2267		let key_byte = 0;
2268		let (mut call_setup, key, input_bytes) =
2269			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Cold)?;
2270		let (mut ext, _) = call_setup.ext();
2271		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2272			.map_err(|_| "Failed to write to storage during setup.")?;
2273
2274		let result;
2275		#[block]
2276		{
2277			result = run_builtin_precompile(
2278				&mut ext,
2279				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2280				input_bytes,
2281			);
2282		}
2283		assert_ok!(result);
2284		assert!(ext.get_storage(&key).is_some());
2285
2286		Ok(())
2287	}
2288
2289	#[benchmark(skip_meta, pov_mode = Measured)]
2290	fn contains_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2291		let key_byte = 0;
2292		let (mut call_setup, key, input_bytes) =
2293			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Hot)?;
2294		let (mut ext, _) = call_setup.ext();
2295		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2296			.map_err(|_| "Failed to write to storage during setup.")?;
2297
2298		ext.touch_storage_access(&key, StorageOp::Read);
2299
2300		let result;
2301		#[block]
2302		{
2303			result = run_builtin_precompile(
2304				&mut ext,
2305				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2306				input_bytes,
2307			);
2308		}
2309		assert_ok!(result);
2310		assert!(ext.get_storage(&key).is_some());
2311
2312		Ok(())
2313	}
2314
2315	#[benchmark(skip_meta, pov_mode = Measured)]
2316	fn take_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2317		let key_byte = 3;
2318		let (mut call_setup, key, input_bytes) =
2319			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Cold)?;
2320		let (mut ext, _) = call_setup.ext();
2321		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2322			.map_err(|_| "Failed to write to storage during setup.")?;
2323
2324		let result;
2325		#[block]
2326		{
2327			result = run_builtin_precompile(
2328				&mut ext,
2329				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2330				input_bytes,
2331			);
2332		}
2333		assert_ok!(result);
2334		assert!(ext.get_storage(&key).is_none());
2335
2336		Ok(())
2337	}
2338
2339	#[benchmark(skip_meta, pov_mode = Measured)]
2340	fn take_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2341		let key_byte = 3;
2342		let (mut call_setup, key, input_bytes) =
2343			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Hot)?;
2344		let (mut ext, _) = call_setup.ext();
2345		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2346			.map_err(|_| "Failed to write to storage during setup.")?;
2347
2348		ext.touch_storage_access(&key, StorageOp::Write);
2349
2350		let result;
2351		#[block]
2352		{
2353			result = run_builtin_precompile(
2354				&mut ext,
2355				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2356				input_bytes,
2357			);
2358		}
2359		assert_ok!(result);
2360		assert!(ext.get_storage(&key).is_none());
2361
2362		Ok(())
2363	}
2364
2365	fn worst_case_slot() -> crate::access_list::Slot {
2366		let key = Key::try_from_var(vec![0xFFu8; limits::STORAGE_KEY_BYTES as usize])
2367			.expect("key fits STORAGE_KEY_BYTES bound; qed");
2368		crate::access_list::Slot::from(&key)
2369	}
2370
2371	fn near_full_access_list() -> crate::access_list::AccessList {
2372		let mut al = AccessList::new();
2373		for i in 0..(MAX_ACCESS_LIST_ENTRIES - 1) {
2374			al.touch(
2375				AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(i as u64) },
2376				StorageOp::Read,
2377			);
2378		}
2379		al
2380	}
2381
2382	#[benchmark(pov_mode = Ignored)]
2383	fn access_list_touch_cold_full() -> Result<(), BenchmarkError> {
2384		let mut al = near_full_access_list();
2385		// Insert a new entry (u64::MAX is past the fill range, so the touch is cold).
2386		let entry =
2387			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2388		let outcome;
2389		#[block]
2390		{
2391			outcome = al.touch(entry, StorageOp::Read);
2392		}
2393		assert!(outcome.is_cold());
2394		Ok(())
2395	}
2396
2397	#[benchmark(pov_mode = Ignored)]
2398	fn access_list_touch_hot_full() -> Result<(), BenchmarkError> {
2399		let mut al = near_full_access_list();
2400		// Worst-case hot touch: the rightmost key and the write upgrades the read-paid entry.
2401		let entry = AccessEntry {
2402			slot: worst_case_slot(),
2403			address: H160::from_low_u64_be(MAX_ACCESS_LIST_ENTRIES as u64 - 2),
2404		};
2405
2406		let touched = entry.clone();
2407		let outcome;
2408		#[block]
2409		{
2410			outcome = al.touch(touched, StorageOp::Write);
2411		}
2412		assert_eq!(
2413			outcome,
2414			Warmth::Hot { charged: StorageOp::Read },
2415			"the fill seeded this entry read-paid"
2416		);
2417		assert_eq!(
2418			al.peek(&entry),
2419			Warmth::Hot { charged: StorageOp::Write },
2420			"the write upgraded the entry"
2421		);
2422		Ok(())
2423	}
2424
2425	#[benchmark(pov_mode = Ignored)]
2426	fn access_list_touch_cold_empty() -> Result<(), BenchmarkError> {
2427		let mut al = AccessList::new();
2428		let entry =
2429			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2430		let outcome;
2431		#[block]
2432		{
2433			outcome = al.touch(entry, StorageOp::Read);
2434		}
2435		assert!(outcome.is_cold());
2436		Ok(())
2437	}
2438
2439	#[benchmark(pov_mode = Ignored)]
2440	fn access_list_touch_hot_single_element() -> Result<(), BenchmarkError> {
2441		let mut al = AccessList::new();
2442		let entry =
2443			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2444		al.touch(entry.clone(), StorageOp::Read);
2445		let outcome;
2446		#[block]
2447		{
2448			outcome = al.touch(entry, StorageOp::Read);
2449		}
2450		assert!(!outcome.is_cold());
2451		Ok(())
2452	}
2453
2454	// Per-entry rollback cost, prepaid by every cold touch since a frame revert
2455	// can't charge gas itself. Isolated by reverting a frame with exactly one
2456	// journaled entry on top of a near-full `AccessList`.
2457	#[benchmark(pov_mode = Ignored)]
2458	fn access_list_rollback_amortization() -> Result<(), BenchmarkError> {
2459		let mut al = near_full_access_list();
2460		al.enter_frame();
2461		al.touch(
2462			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) },
2463			StorageOp::Read,
2464		);
2465		#[block]
2466		{
2467			al.rollback_frame();
2468		}
2469		Ok(())
2470	}
2471
2472	// We use both full and empty benchmarks here instead of benchmarking transient_storage
2473	// (BTreeMap) directly. This approach is necessary because benchmarking this BTreeMap is very
2474	// slow. Additionally, we use linear regression for our benchmarks, and the BTreeMap's log(n)
2475	// complexity can introduce approximation errors.
2476	#[benchmark(pov_mode = Ignored)]
2477	fn set_transient_storage_empty() -> Result<(), BenchmarkError> {
2478		let max_value_len = limits::STORAGE_BYTES;
2479		let max_key_len = limits::STORAGE_KEY_BYTES;
2480		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2481			.map_err(|_| "Key has wrong length")?;
2482		let value = Some(vec![42u8; max_value_len as _]);
2483		let mut setup = CallSetup::<T>::default();
2484		let (mut ext, _) = setup.ext();
2485		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2486		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2487		let result;
2488		#[block]
2489		{
2490			result = runtime.ext().set_transient_storage(&key, value, false);
2491		}
2492
2493		assert_eq!(result, Ok(WriteOutcome::New));
2494		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2495		Ok(())
2496	}
2497
2498	#[benchmark(pov_mode = Ignored)]
2499	fn set_transient_storage_full() -> Result<(), BenchmarkError> {
2500		let max_value_len = limits::STORAGE_BYTES;
2501		let max_key_len = limits::STORAGE_KEY_BYTES;
2502		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2503			.map_err(|_| "Key has wrong length")?;
2504		let value = Some(vec![42u8; max_value_len as _]);
2505		let mut setup = CallSetup::<T>::default();
2506		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2507		let (mut ext, _) = setup.ext();
2508		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2509		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2510		let result;
2511		#[block]
2512		{
2513			result = runtime.ext().set_transient_storage(&key, value, false);
2514		}
2515
2516		assert_eq!(result, Ok(WriteOutcome::New));
2517		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2518		Ok(())
2519	}
2520
2521	#[benchmark(pov_mode = Ignored)]
2522	fn get_transient_storage_empty() -> Result<(), BenchmarkError> {
2523		let max_value_len = limits::STORAGE_BYTES;
2524		let max_key_len = limits::STORAGE_KEY_BYTES;
2525		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2526			.map_err(|_| "Key has wrong length")?;
2527
2528		let mut setup = CallSetup::<T>::default();
2529		let (mut ext, _) = setup.ext();
2530		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2531		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2532		runtime
2533			.ext()
2534			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2535			.map_err(|_| "Failed to write to transient storage during setup.")?;
2536		let result;
2537		#[block]
2538		{
2539			result = runtime.ext().get_transient_storage(&key);
2540		}
2541
2542		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2543		Ok(())
2544	}
2545
2546	#[benchmark(pov_mode = Ignored)]
2547	fn get_transient_storage_full() -> Result<(), BenchmarkError> {
2548		let max_value_len = limits::STORAGE_BYTES;
2549		let max_key_len = limits::STORAGE_KEY_BYTES;
2550		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2551			.map_err(|_| "Key has wrong length")?;
2552
2553		let mut setup = CallSetup::<T>::default();
2554		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2555		let (mut ext, _) = setup.ext();
2556		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2557		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2558		runtime
2559			.ext()
2560			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2561			.map_err(|_| "Failed to write to transient storage during setup.")?;
2562		let result;
2563		#[block]
2564		{
2565			result = runtime.ext().get_transient_storage(&key);
2566		}
2567
2568		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2569		Ok(())
2570	}
2571
2572	// The weight of journal rollbacks should be taken into account when setting storage.
2573	#[benchmark(pov_mode = Ignored)]
2574	fn rollback_transient_storage() -> Result<(), BenchmarkError> {
2575		let max_value_len = limits::STORAGE_BYTES;
2576		let max_key_len = limits::STORAGE_KEY_BYTES;
2577		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2578			.map_err(|_| "Key has wrong length")?;
2579
2580		let mut setup = CallSetup::<T>::default();
2581		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2582		let (mut ext, _) = setup.ext();
2583		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2584		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2585		runtime.ext().transient_storage().start_transaction();
2586		runtime
2587			.ext()
2588			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2589			.map_err(|_| "Failed to write to transient storage during setup.")?;
2590		#[block]
2591		{
2592			runtime.ext().transient_storage().rollback_transaction();
2593		}
2594
2595		assert_eq!(runtime.ext().get_transient_storage(&key), None);
2596		Ok(())
2597	}
2598
2599	// n: new byte size
2600	// o: old byte size
2601	#[benchmark(pov_mode = Measured)]
2602	fn seal_set_transient_storage(
2603		n: Linear<0, { limits::STORAGE_BYTES }>,
2604		o: Linear<0, { limits::STORAGE_BYTES }>,
2605	) -> Result<(), BenchmarkError> {
2606		let max_key_len = limits::STORAGE_KEY_BYTES;
2607		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2608			.map_err(|_| "Key has wrong length")?;
2609		let value = vec![1u8; n as usize];
2610		build_runtime!(runtime, memory: [ key.unhashed(), value.clone(), ]);
2611		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2612		runtime
2613			.ext()
2614			.set_transient_storage(&key, Some(vec![42u8; o as usize]), false)
2615			.map_err(|_| "Failed to write to transient storage during setup.")?;
2616
2617		let result;
2618		#[block]
2619		{
2620			result = runtime.bench_set_storage(
2621				memory.as_mut_slice(),
2622				StorageFlags::TRANSIENT.bits(),
2623				0,           // key_ptr
2624				max_key_len, // key_len
2625				max_key_len, // value_ptr
2626				n,           // value_len
2627			);
2628		}
2629
2630		assert_ok!(result);
2631		assert_eq!(runtime.ext().get_transient_storage(&key).unwrap(), value);
2632		Ok(())
2633	}
2634
2635	#[benchmark(pov_mode = Measured)]
2636	fn seal_clear_transient_storage(
2637		n: Linear<0, { limits::STORAGE_BYTES }>,
2638	) -> Result<(), BenchmarkError> {
2639		let max_key_len = limits::STORAGE_KEY_BYTES;
2640		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2641			.map_err(|_| "Key has wrong length")?;
2642		let input_bytes = IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
2643			flags: StorageFlags::TRANSIENT.bits(),
2644			key: vec![0u8; max_key_len as usize].into(),
2645			isFixedKey: false,
2646		})
2647		.abi_encode();
2648
2649		let mut call_setup = CallSetup::<T>::default();
2650		let (mut ext, _) = call_setup.ext();
2651		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2652			.map_err(|_| "Failed to write to transient storage during setup.")?;
2653
2654		let result;
2655		#[block]
2656		{
2657			result = run_builtin_precompile(
2658				&mut ext,
2659				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2660				input_bytes,
2661			);
2662		}
2663		assert_ok!(result);
2664		assert!(ext.get_transient_storage(&key).is_none());
2665
2666		Ok(())
2667	}
2668
2669	#[benchmark(pov_mode = Measured)]
2670	fn seal_get_transient_storage(
2671		n: Linear<0, { limits::STORAGE_BYTES }>,
2672	) -> Result<(), BenchmarkError> {
2673		let max_key_len = limits::STORAGE_KEY_BYTES;
2674		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2675			.map_err(|_| "Key has wrong length")?;
2676		build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2677		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2678		runtime
2679			.ext()
2680			.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2681			.map_err(|_| "Failed to write to transient storage during setup.")?;
2682
2683		let out_ptr = max_key_len + 4;
2684		let result;
2685		#[block]
2686		{
2687			result = runtime.bench_get_storage(
2688				memory.as_mut_slice(),
2689				StorageFlags::TRANSIENT.bits(),
2690				0,           // key_ptr
2691				max_key_len, // key_len
2692				out_ptr,     // out_ptr
2693				max_key_len, // out_len_ptr
2694			);
2695		}
2696
2697		assert_ok!(result);
2698		assert_eq!(
2699			&runtime.ext().get_transient_storage(&key).unwrap(),
2700			&memory[out_ptr as usize..]
2701		);
2702		Ok(())
2703	}
2704
2705	#[benchmark(pov_mode = Measured)]
2706	fn seal_contains_transient_storage(
2707		n: Linear<0, { limits::STORAGE_BYTES }>,
2708	) -> Result<(), BenchmarkError> {
2709		let max_key_len = limits::STORAGE_KEY_BYTES;
2710		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2711			.map_err(|_| "Key has wrong length")?;
2712
2713		let input_bytes = IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
2714			flags: StorageFlags::TRANSIENT.bits(),
2715			key: vec![0u8; max_key_len as usize].into(),
2716			isFixedKey: false,
2717		})
2718		.abi_encode();
2719
2720		let mut call_setup = CallSetup::<T>::default();
2721		let (mut ext, _) = call_setup.ext();
2722		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2723			.map_err(|_| "Failed to write to transient storage during setup.")?;
2724
2725		let result;
2726		#[block]
2727		{
2728			result = run_builtin_precompile(
2729				&mut ext,
2730				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2731				input_bytes,
2732			);
2733		}
2734		assert!(result.is_ok());
2735		assert!(ext.get_transient_storage(&key).is_some());
2736
2737		Ok(())
2738	}
2739
2740	#[benchmark(pov_mode = Measured)]
2741	fn seal_take_transient_storage(
2742		n: Linear<0, { limits::STORAGE_BYTES }>,
2743	) -> Result<(), BenchmarkError> {
2744		let n = limits::STORAGE_BYTES;
2745		let value = vec![42u8; n as usize];
2746		let max_key_len = limits::STORAGE_KEY_BYTES;
2747		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2748			.map_err(|_| "Key has wrong length")?;
2749
2750		let input_bytes = IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
2751			flags: StorageFlags::TRANSIENT.bits(),
2752			key: vec![0u8; max_key_len as usize].into(),
2753			isFixedKey: false,
2754		})
2755		.abi_encode();
2756
2757		let mut call_setup = CallSetup::<T>::default();
2758		let (mut ext, _) = call_setup.ext();
2759		ext.set_transient_storage(&key, Some(value), false)
2760			.map_err(|_| "Failed to write to transient storage during setup.")?;
2761
2762		let result;
2763		#[block]
2764		{
2765			result = run_builtin_precompile(
2766				&mut ext,
2767				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2768				input_bytes,
2769			);
2770		}
2771		assert!(result.is_ok());
2772		assert!(ext.get_transient_storage(&key).is_none());
2773
2774		Ok(())
2775	}
2776
2777	// t: with or without some value to transfer
2778	// d: with or without dust value to transfer
2779	// i: size of the input data
2780	#[benchmark(pov_mode = Measured)]
2781	fn seal_call(
2782		t: Linear<0, 1>,
2783		d: Linear<0, 1>,
2784		i: Linear<0, { limits::code::BLOB_BYTES }>,
2785	) -> Result<(), BenchmarkError> {
2786		// An EIP-7702 delegated callee is the worst case for the account resolution in
2787		// `new_frame`: the `AccountInfoOf` entry decodes the larger `DelegatedEOA` variant and
2788		// the call still runs the target's code. (A callee whose delegation snapshot is empty
2789		// costs a second read of the target but skips code load and execution entirely, so it
2790		// is cheaper overall.)
2791		let target = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?;
2792		let callee_addr = H160([0x42; 20]);
2793		let callee = delegated_eoa::<T>(callee_addr, target.address)?;
2794		// Keep the origin's budget the same as with a contract callee. A contract already exists
2795		// in `System`, so `Stack::transfer` skips the "create the destination" arm; a fresh EOA
2796		// does not, and that arm charges the destination's ED to the origin, leaving it nothing
2797		// for `ensure_sufficient_dust` to burn into dust when `d == 1`.
2798		T::Currency::set_balance(&callee, Pallet::<T>::min_balance());
2799
2800		let callee_bytes = callee.encode();
2801		let callee_len = callee_bytes.len() as u32;
2802
2803		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2804		let dust = 100u32 * d;
2805		let evm_value =
2806			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2807		let value_bytes = evm_value.encode();
2808
2809		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2810		let deposit_bytes = Into::<U256>::into(deposit).encode();
2811		let deposit_len = deposit_bytes.len() as u32;
2812
2813		let mut setup = CallSetup::<T>::default();
2814		setup.set_storage_deposit_limit(deposit);
2815		// We benchmark the overhead of cloning the input. Not passing it to the contract.
2816		// This is why we set the input here instead of passig it as pointer to the `bench_call`.
2817		setup.set_data(vec![42; i as usize]);
2818		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2819		setup.set_balance(value + 1u32.into() + Pallet::<T>::min_balance());
2820
2821		let (mut ext, _) = setup.ext();
2822		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2823		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,);
2824		let before = Pallet::<T>::evm_balance(&callee_addr);
2825
2826		let result;
2827		#[block]
2828		{
2829			result = runtime.bench_call(
2830				memory.as_mut_slice(),
2831				pack_hi_lo(CallFlags::CLONE_INPUT.bits(), 0), // flags + callee
2832				u64::MAX,                                     // ref_time_limit
2833				u64::MAX,                                     // proof_size_limit
2834				pack_hi_lo(callee_len, callee_len + deposit_len), // deposit_ptr + value_pr
2835				pack_hi_lo(0, 0),                             // input len + data ptr
2836				pack_hi_lo(0, SENTINEL),                      // output len + data ptr
2837			);
2838		}
2839
2840		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2841		assert_eq!(
2842			Pallet::<T>::evm_balance(&callee_addr),
2843			before + evm_value,
2844			"{callee_addr:?} balance should have grown by {evm_value:?}"
2845		);
2846
2847		Ok(())
2848	}
2849
2850	// d: 1 if the associated pre-compile has a contract info that needs to be loaded
2851	// i: size of the input data
2852	#[benchmark(pov_mode = Measured)]
2853	fn seal_call_precompile(d: Linear<0, 1>, i: Linear<0, { limits::CALLDATA_BYTES - 100 }>) {
2854		use alloy_core::sol_types::SolInterface;
2855		use precompiles::{BenchmarkNoInfo, BenchmarkWithInfo, BuiltinPrecompile, IBenchmarking};
2856
2857		let callee_bytes = if d == 1 {
2858			BenchmarkWithInfo::<T>::MATCHER.base_address().to_vec()
2859		} else {
2860			BenchmarkNoInfo::<T>::MATCHER.base_address().to_vec()
2861		};
2862		let callee_len = callee_bytes.len() as u32;
2863
2864		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2865		let deposit_bytes = Into::<U256>::into(deposit).encode();
2866		let deposit_len = deposit_bytes.len() as u32;
2867
2868		let value: BalanceOf<T> = Zero::zero();
2869		let value_bytes = Into::<U256>::into(value).encode();
2870		let value_len = value_bytes.len() as u32;
2871
2872		let input_bytes = IBenchmarking::IBenchmarkingCalls::bench(IBenchmarking::benchCall {
2873			input: vec![42_u8; i as usize].into(),
2874		})
2875		.abi_encode();
2876		let input_len = input_bytes.len() as u32;
2877
2878		let mut setup = CallSetup::<T>::default();
2879		setup.set_storage_deposit_limit(deposit);
2880
2881		let (mut ext, _) = setup.ext();
2882		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2883		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes, input_bytes,);
2884
2885		let mut do_benchmark = || {
2886			runtime.bench_call(
2887				memory.as_mut_slice(),
2888				pack_hi_lo(0, 0), // flags + callee
2889				u64::MAX,         // ref_time_limit
2890				u64::MAX,         // proof_size_limit
2891				pack_hi_lo(callee_len, callee_len + deposit_len), /* deposit_ptr +
2892				                   * value_pr */
2893				pack_hi_lo(input_len, callee_len + deposit_len + value_len), /* input len +
2894				                                                              * input ptr */
2895				pack_hi_lo(0, SENTINEL), // output len + output ptr
2896			)
2897		};
2898
2899		// first call of the pre-compile will create its contract info and account
2900		// so we make sure to create it
2901		assert_eq!(do_benchmark().unwrap(), ReturnErrorCode::Success);
2902
2903		let result;
2904		#[block]
2905		{
2906			result = do_benchmark();
2907		}
2908
2909		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2910	}
2911
2912	#[benchmark(pov_mode = Measured)]
2913	fn seal_delegate_call() -> Result<(), BenchmarkError> {
2914		// Delegated code source: worst case for the callee resolution in `new_frame`.
2915		// See `seal_call`.
2916		let target = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?;
2917		let address = delegated_eoa::<T>(H160([0x43; 20]), target.address)?;
2918
2919		let address_bytes = address.encode();
2920		let address_len = address_bytes.len() as u32;
2921
2922		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2923		let deposit_bytes = Into::<U256>::into(deposit).encode();
2924
2925		let mut setup = CallSetup::<T>::default();
2926		setup.set_storage_deposit_limit(deposit);
2927		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2928
2929		let (mut ext, _) = setup.ext();
2930		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2931		let mut memory = memory!(address_bytes, deposit_bytes,);
2932
2933		let result;
2934		#[block]
2935		{
2936			result = runtime.bench_delegate_call(
2937				memory.as_mut_slice(),
2938				pack_hi_lo(0, 0),        // flags + address ptr
2939				u64::MAX,                // ref_time_limit
2940				u64::MAX,                // proof_size_limit
2941				address_len,             // deposit_ptr
2942				pack_hi_lo(0, 0),        // input len + data ptr
2943				pack_hi_lo(0, SENTINEL), // output len + ptr
2944			);
2945		}
2946
2947		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2948		Ok(())
2949	}
2950
2951	// t: with or without some value to transfer
2952	// d: with or without dust value to transfer
2953	// i: size of the input data
2954	#[benchmark(pov_mode = Measured)]
2955	fn seal_instantiate(
2956		t: Linear<0, 1>,
2957		d: Linear<0, 1>,
2958		i: Linear<0, { limits::CALLDATA_BYTES }>,
2959	) -> Result<(), BenchmarkError> {
2960		let code = VmBinaryModule::dummy();
2961		let hash = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?.info()?.code_hash;
2962		let hash_bytes = hash.encode();
2963
2964		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2965		let dust = 100u32 * d;
2966		let evm_value =
2967			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2968		let value_bytes = evm_value.encode();
2969		let value_len = value_bytes.len() as u32;
2970
2971		let deposit: BalanceOf<T> = BalanceOf::<T>::max_value();
2972		let deposit_bytes = Into::<U256>::into(deposit).encode();
2973		let deposit_len = deposit_bytes.len() as u32;
2974
2975		let mut setup = CallSetup::<T>::default();
2976		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2977		setup.set_balance(value + 1u32.into() + (Pallet::<T>::min_balance() * 2u32.into()));
2978
2979		let account_id = &setup.contract().account_id.clone();
2980		let (mut ext, _) = setup.ext();
2981		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2982
2983		let input = vec![42u8; i as _];
2984		let input_len = hash_bytes.len() as u32 + input.len() as u32;
2985		let salt = [42u8; 32];
2986		let deployer = T::AddressMapper::to_address(&account_id);
2987		let addr = crate::address::create2(&deployer, &code.code, &input, &salt);
2988		let mut memory = memory!(hash_bytes, input, deposit_bytes, value_bytes, salt,);
2989
2990		let mut offset = {
2991			let mut current = 0u32;
2992			move |after: u32| {
2993				current += after;
2994				current
2995			}
2996		};
2997
2998		assert!(AccountInfoOf::<T>::get(&addr).is_none());
2999
3000		let result;
3001		#[block]
3002		{
3003			result = runtime.bench_instantiate(
3004				memory.as_mut_slice(),
3005				u64::MAX,                                           // ref_time_limit
3006				u64::MAX,                                           // proof_size_limit
3007				pack_hi_lo(offset(input_len), offset(deposit_len)), // deposit_ptr + value_ptr
3008				pack_hi_lo(input_len, 0),                           // input_data_len + input_data
3009				pack_hi_lo(0, SENTINEL),                            // output_len_ptr + output_ptr
3010				pack_hi_lo(SENTINEL, offset(value_len)),            // address_ptr + salt_ptr
3011			);
3012		}
3013
3014		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
3015		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
3016
3017		assert_eq!(
3018			Pallet::<T>::evm_balance(&addr),
3019			evm_value,
3020			"{addr:?} balance should hold {evm_value:?}"
3021		);
3022		Ok(())
3023	}
3024
3025	// t: with or without some value to transfer
3026	// d: with or without dust value to transfer
3027	// i: size of the init code (max 49152 bytes per EIP-3860)
3028	#[benchmark(pov_mode = Measured)]
3029	fn evm_instantiate(
3030		t: Linear<0, 1>,
3031		d: Linear<0, 1>,
3032		i: Linear<{ 10 * 1024 }, { 48 * 1024 }>,
3033	) -> Result<(), BenchmarkError> {
3034		use crate::vm::evm::instructions::BENCH_INIT_CODE;
3035		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_init_code_for_runtime_size(0));
3036		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
3037		setup.set_balance(caller_funding::<T>());
3038
3039		let (mut ext, _) = setup.ext();
3040		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
3041
3042		let value = {
3043			let value: BalanceOf<T> = (1_000_000u32 * t).into();
3044			let dust = 100u32 * d;
3045			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust))
3046		};
3047
3048		let init_code = vec![BENCH_INIT_CODE; i as usize];
3049		let _ = interpreter.memory.resize(0, init_code.len());
3050		let salt = U256::from(42u64);
3051		interpreter.memory.set_data(0, 0, init_code.len(), &init_code);
3052
3053		// Setup stack for create instruction [value, offset, size, salt]
3054		let _ = interpreter.stack.push(salt);
3055		let _ = interpreter.stack.push(U256::from(init_code.len()));
3056		let _ = interpreter.stack.push(U256::zero());
3057		let _ = interpreter.stack.push(value);
3058
3059		let result;
3060		#[block]
3061		{
3062			result = instructions::contract::create::<true, _>(&mut interpreter);
3063		}
3064
3065		assert!(result.is_continue());
3066		let addr = interpreter.stack.top().unwrap().into_address();
3067		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
3068		assert_eq!(Pallet::<T>::code(&addr).len(), revm::primitives::eip170::MAX_CODE_SIZE);
3069		assert_eq!(Pallet::<T>::evm_balance(&addr), value, "balance should hold {value:?}");
3070		Ok(())
3071	}
3072
3073	// `n`: Input to hash in bytes
3074	#[benchmark(pov_mode = Measured)]
3075	fn sha2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3076		let input = vec![0u8; n as usize];
3077		let mut call_setup = CallSetup::<T>::default();
3078		let (mut ext, _) = call_setup.ext();
3079
3080		let result;
3081		#[block]
3082		{
3083			result = run_builtin_precompile(
3084				&mut ext,
3085				H160::from_low_u64_be(2).as_fixed_bytes(),
3086				input.clone(),
3087			);
3088		}
3089		assert_eq!(sp_io::hashing::sha2_256(&input).to_vec(), result.unwrap().data);
3090	}
3091
3092	#[benchmark(pov_mode = Measured)]
3093	fn identity(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3094		let input = vec![0u8; n as usize];
3095		let mut call_setup = CallSetup::<T>::default();
3096		let (mut ext, _) = call_setup.ext();
3097
3098		let result;
3099		#[block]
3100		{
3101			result = run_builtin_precompile(
3102				&mut ext,
3103				H160::from_low_u64_be(4).as_fixed_bytes(),
3104				input.clone(),
3105			);
3106		}
3107		assert_eq!(input, result.unwrap().data);
3108	}
3109
3110	// `n`: Input to hash in bytes
3111	#[benchmark(pov_mode = Measured)]
3112	fn ripemd_160(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3113		use ripemd::Digest;
3114		let input = vec![0u8; n as usize];
3115		let mut call_setup = CallSetup::<T>::default();
3116		let (mut ext, _) = call_setup.ext();
3117
3118		let result;
3119		#[block]
3120		{
3121			result = run_builtin_precompile(
3122				&mut ext,
3123				H160::from_low_u64_be(3).as_fixed_bytes(),
3124				input.clone(),
3125			);
3126		}
3127		let mut expected = [0u8; 32];
3128		expected[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input));
3129
3130		assert_eq!(expected.to_vec(), result.unwrap().data);
3131	}
3132
3133	// `n`: Input to hash in bytes
3134	#[benchmark(pov_mode = Measured)]
3135	fn seal_hash_keccak_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3136		build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]);
3137
3138		let result;
3139		#[block]
3140		{
3141			result = runtime.bench_hash_keccak_256(memory.as_mut_slice(), 32, n, 0);
3142		}
3143		assert_eq!(sp_io::hashing::keccak_256(&memory[32..]), &memory[0..32]);
3144		assert_ok!(result);
3145	}
3146
3147	// `n`: Input to hash in bytes
3148	#[benchmark(pov_mode = Measured)]
3149	fn hash_blake2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3150		let input = vec![0u8; n as usize];
3151		let input_bytes = ISystem::ISystemCalls::hashBlake256(ISystem::hashBlake256Call {
3152			input: input.clone().into(),
3153		})
3154		.abi_encode();
3155
3156		let mut call_setup = CallSetup::<T>::default();
3157		let (mut ext, _) = call_setup.ext();
3158
3159		let result;
3160		#[block]
3161		{
3162			result = run_builtin_precompile(
3163				&mut ext,
3164				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
3165				input_bytes,
3166			);
3167		}
3168		let truth: [u8; 32] = sp_io::hashing::blake2_256(&input);
3169		let truth = FixedBytes::<32>::abi_encode(&truth);
3170		let truth = FixedBytes::<32>::abi_decode(&truth[..]).expect("decoding failed");
3171
3172		let raw_data = result.unwrap().data;
3173		let ret_hash = FixedBytes::<32>::abi_decode(&raw_data[..]).expect("decoding failed");
3174		assert_eq!(truth, ret_hash);
3175	}
3176
3177	// `n`: Input to hash in bytes
3178	#[benchmark(pov_mode = Measured)]
3179	fn hash_blake2_128(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3180		let input = vec![0u8; n as usize];
3181		let input_bytes = ISystem::ISystemCalls::hashBlake128(ISystem::hashBlake128Call {
3182			input: input.clone().into(),
3183		})
3184		.abi_encode();
3185
3186		let mut call_setup = CallSetup::<T>::default();
3187		let (mut ext, _) = call_setup.ext();
3188
3189		let result;
3190		#[block]
3191		{
3192			result = run_builtin_precompile(
3193				&mut ext,
3194				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
3195				input_bytes,
3196			);
3197		}
3198		let truth: [u8; 16] = sp_io::hashing::blake2_128(&input);
3199		let truth = FixedBytes::<16>::abi_encode(&truth);
3200		let truth = FixedBytes::<16>::abi_decode(&truth[..]).expect("decoding failed");
3201
3202		let raw_data = result.unwrap().data;
3203		let ret_hash = FixedBytes::<16>::abi_decode(&raw_data[..]).expect("decoding failed");
3204		assert_eq!(truth, ret_hash);
3205	}
3206
3207	// `n`: Message input length to verify in bytes.
3208	// need some buffer so the code size does not exceed the max code size.
3209	#[benchmark(pov_mode = Measured)]
3210	fn seal_sr25519_verify(n: Linear<0, { limits::code::BLOB_BYTES - 255 }>) {
3211		let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::<Vec<_>>();
3212		let message_len = message.len() as u32;
3213
3214		let key_type = sp_core::crypto::KeyTypeId(*b"code");
3215		let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
3216		let sig =
3217			sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
3218		let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec();
3219		let sig_len = sig.len() as u32;
3220
3221		build_runtime!(runtime, memory: [sig, pub_key.to_vec(), message, ]);
3222
3223		let result;
3224		#[block]
3225		{
3226			result = runtime.bench_sr25519_verify(
3227				memory.as_mut_slice(),
3228				0,                              // signature_ptr
3229				sig_len,                        // pub_key_ptr
3230				message_len,                    // message_len
3231				sig_len + pub_key.len() as u32, // message_ptr
3232			);
3233		}
3234
3235		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
3236	}
3237
3238	#[benchmark(pov_mode = Measured)]
3239	fn ecdsa_recover() {
3240		use hex_literal::hex;
3241		let input = hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549").to_vec();
3242		let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b");
3243		let mut call_setup = CallSetup::<T>::default();
3244		let (mut ext, _) = call_setup.ext();
3245
3246		let result;
3247
3248		#[block]
3249		{
3250			result =
3251				run_builtin_precompile(&mut ext, H160::from_low_u64_be(1).as_fixed_bytes(), input);
3252		}
3253
3254		assert_eq!(result.unwrap().data, expected);
3255	}
3256
3257	#[benchmark(pov_mode = Measured)]
3258	fn p256_verify() {
3259		use hex_literal::hex;
3260		let input = hex!("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").to_vec();
3261		let expected = U256::one().to_big_endian();
3262		let mut call_setup = CallSetup::<T>::default();
3263		let (mut ext, _) = call_setup.ext();
3264
3265		let result;
3266
3267		#[block]
3268		{
3269			result = run_builtin_precompile(
3270				&mut ext,
3271				H160::from_low_u64_be(0x100).as_fixed_bytes(),
3272				input,
3273			);
3274		}
3275
3276		assert_eq!(result.unwrap().data, expected);
3277	}
3278
3279	#[benchmark(pov_mode = Measured)]
3280	fn bn128_add() {
3281		use hex_literal::hex;
3282		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b3625f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd5850b38c7ced6e4daef9c4347f370d6d8b58f4b1d8dc61a3c59d651a0644a2a27cf").to_vec();
3283		let expected = hex!(
3284			"0a6678fd675aa4d8f0d03a1feb921a27f38ebdcb860cc083653519655acd6d79172fd5b3b2bfdd44e43bcec3eace9347608f9f0a16f1e184cb3f52e6f259cbeb"
3285		);
3286		let mut call_setup = CallSetup::<T>::default();
3287		let (mut ext, _) = call_setup.ext();
3288
3289		let result;
3290		#[block]
3291		{
3292			result =
3293				run_builtin_precompile(&mut ext, H160::from_low_u64_be(6).as_fixed_bytes(), input);
3294		}
3295
3296		assert_eq!(result.unwrap().data, expected);
3297	}
3298
3299	#[benchmark(pov_mode = Measured)]
3300	fn bn128_mul() {
3301		use hex_literal::hex;
3302		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").to_vec();
3303		let expected = hex!(
3304			"0bf982b98a2757878c051bfe7eee228b12bc69274b918f08d9fcb21e9184ddc10b17c77cbf3c19d5d27e18cbd4a8c336afb488d0e92c18d56e64dd4ea5c437e6"
3305		);
3306		let mut call_setup = CallSetup::<T>::default();
3307		let (mut ext, _) = call_setup.ext();
3308
3309		let result;
3310		#[block]
3311		{
3312			result =
3313				run_builtin_precompile(&mut ext, H160::from_low_u64_be(7).as_fixed_bytes(), input);
3314		}
3315
3316		assert_eq!(result.unwrap().data, expected);
3317	}
3318
3319	// `n`: pairings to perform
3320	#[benchmark(pov_mode = Measured)]
3321	fn bn128_pairing(n: Linear<0, { 20 }>) {
3322		fn generate_random_ecpairs(n: usize) -> Vec<u8> {
3323			use bn::{AffineG1, AffineG2, Fr, G1, G2, Group};
3324			use rand::SeedableRng;
3325			use rand_pcg::Pcg64;
3326			let mut rng = Pcg64::seed_from_u64(1);
3327
3328			let mut buffer = vec![0u8; n * 192];
3329
3330			let mut write = |element: &bn::Fq, offset: &mut usize| {
3331				element.to_big_endian(&mut buffer[*offset..*offset + 32]).unwrap();
3332				*offset += 32
3333			};
3334
3335			for i in 0..n {
3336				let mut offset = i * 192;
3337				let scalar = Fr::random(&mut rng);
3338
3339				let g1 = G1::one() * scalar;
3340				let g2 = G2::one() * scalar;
3341				let a = AffineG1::from_jacobian(g1).expect("G1 point should be on curve");
3342				let b = AffineG2::from_jacobian(g2).expect("G2 point should be on curve");
3343
3344				write(&a.x(), &mut offset);
3345				write(&a.y(), &mut offset);
3346				write(&b.x().imaginary(), &mut offset);
3347				write(&b.x().real(), &mut offset);
3348				write(&b.y().imaginary(), &mut offset);
3349				write(&b.y().real(), &mut offset);
3350			}
3351
3352			buffer
3353		}
3354
3355		let input = generate_random_ecpairs(n as usize);
3356		let mut call_setup = CallSetup::<T>::default();
3357		let (mut ext, _) = call_setup.ext();
3358
3359		let result;
3360		#[block]
3361		{
3362			result =
3363				run_builtin_precompile(&mut ext, H160::from_low_u64_be(8).as_fixed_bytes(), input);
3364		}
3365		assert_ok!(result);
3366	}
3367
3368	// `n`: number of rounds to perform
3369	#[benchmark(pov_mode = Measured)]
3370	fn blake2f(n: Linear<0, 1200>) {
3371		use hex_literal::hex;
3372		let input = hex!(
3373			"48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"
3374		);
3375		let input = n.to_be_bytes().to_vec().into_iter().chain(input.to_vec()).collect::<Vec<_>>();
3376		let mut call_setup = CallSetup::<T>::default();
3377		let (mut ext, _) = call_setup.ext();
3378
3379		let result;
3380		#[block]
3381		{
3382			result =
3383				run_builtin_precompile(&mut ext, H160::from_low_u64_be(9).as_fixed_bytes(), input);
3384		}
3385		assert_ok!(result);
3386	}
3387
3388	// Only calling the function itself for the list of
3389	// generated different ECDSA keys.
3390	// This is a slow call: We reduce the number of runs.
3391	#[benchmark(pov_mode = Measured)]
3392	fn seal_ecdsa_to_eth_address() {
3393		let key_type = sp_core::crypto::KeyTypeId(*b"code");
3394		let pub_key_bytes = sp_io::crypto::ecdsa_generate(key_type, None).0;
3395		build_runtime!(runtime, memory: [[0u8; 20], pub_key_bytes,]);
3396
3397		let result;
3398		#[block]
3399		{
3400			result = runtime.bench_ecdsa_to_eth_address(
3401				memory.as_mut_slice(),
3402				20, // key_ptr
3403				0,  // output_ptr
3404			);
3405		}
3406
3407		assert_ok!(result);
3408		assert_eq!(&memory[..20], runtime.ext().ecdsa_to_eth_address(&pub_key_bytes).unwrap());
3409	}
3410
3411	/// Benchmark the cost of executing `r` noop (JUMPDEST) instructions.
3412	#[benchmark(pov_mode = Measured)]
3413	fn evm_opcode(r: Linear<0, 10_000>) -> Result<(), BenchmarkError> {
3414		let module = VmBinaryModule::evm_noop(r);
3415		let inputs = vec![];
3416
3417		let code = Bytecode::new_raw(revm::primitives::Bytes::from(module.code.clone()));
3418		let mut setup = CallSetup::<T>::new(module);
3419		let (mut ext, _) = setup.ext();
3420
3421		let result;
3422		#[block]
3423		{
3424			result = evm::call(code, &mut ext, inputs);
3425		}
3426
3427		assert!(result.is_ok());
3428		Ok(())
3429	}
3430
3431	/// Benchmarks `r` EVM `POP` op-codes.
3432	#[benchmark(pov_mode = Measured)]
3433	fn evm_pop_opcode(r: Linear<0, EVM_STACK_LIMIT>) {
3434		let code = Bytecode::new_raw(vec![POP; r as usize].into());
3435		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3436		let (mut ext, _) = setup.ext();
3437		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3438		setup_stack(&mut interpreter, vec![U256::MAX; r as usize]);
3439
3440		evict_caches();
3441		let result;
3442		#[block]
3443		{
3444			result = evm::run_plain(&mut interpreter);
3445		}
3446
3447		let ControlFlow::Break(halt) = result;
3448		assert!(matches!(halt, Halt::Stop));
3449		assert_eq!(interpreter.stack.len(), 0);
3450	}
3451
3452	/// Benchmarks `r` EVM `DIV` op-codes.
3453	///
3454	/// # Added Overheads
3455	///
3456	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3457	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3458	///   the divisor is picked at random from [`knuth_division_operands`].
3459	#[benchmark(pov_mode = Measured)]
3460	fn evm_div_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3461		use knuth_division_operands::DENOMINATORS;
3462
3463		let mut rng = Pcg64::seed_from_u64(1337);
3464		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3465
3466		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3467		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3468		let (mut ext, _) = setup.ext();
3469		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3470		setup_stack(&mut interpreter, operands);
3471
3472		evict_caches();
3473		let result;
3474		#[block]
3475		{
3476			result = evm::run_plain(&mut interpreter);
3477		}
3478
3479		let ControlFlow::Break(halt) = result;
3480		assert!(matches!(halt, Halt::Stop));
3481		assert_eq!(interpreter.stack.len(), 0);
3482	}
3483
3484	/// Benchmarks `r` EVM `DIV` op-codes with pseudo-random one-limb correction patterns.
3485	#[benchmark(pov_mode = Measured)]
3486	fn evm_div_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3487		use one_limb_division_operands::DENOMINATORS;
3488
3489		let mut rng = Pcg64::seed_from_u64(1337);
3490		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3491
3492		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3493		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3494		let (mut ext, _) = setup.ext();
3495		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3496		setup_stack(&mut interpreter, operands);
3497
3498		evict_caches();
3499		let result;
3500		#[block]
3501		{
3502			result = evm::run_plain(&mut interpreter);
3503		}
3504
3505		let ControlFlow::Break(halt) = result;
3506		assert!(matches!(halt, Halt::Stop));
3507		assert_eq!(interpreter.stack.len(), 0);
3508	}
3509
3510	/// Benchmarks `r` EVM `DIV` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3511	#[benchmark(pov_mode = Measured)]
3512	fn evm_div_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3513		let mut rng = Pcg64::seed_from_u64(1337);
3514		let operands = (0..r).flat_map(|_| mixed_division_operands::unsigned_operands(&mut rng));
3515
3516		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3517		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3518		let (mut ext, _) = setup.ext();
3519		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3520		setup_stack(&mut interpreter, operands);
3521
3522		evict_caches();
3523		let result;
3524		#[block]
3525		{
3526			result = evm::run_plain(&mut interpreter);
3527		}
3528
3529		let ControlFlow::Break(halt) = result;
3530		assert!(matches!(halt, Halt::Stop));
3531		assert_eq!(interpreter.stack.len(), 0);
3532	}
3533
3534	/// Benchmarks `r` EVM `DIV` op-codes with the selected fixed Knuth divisor.
3535	#[benchmark(pov_mode = Measured)]
3536	fn evm_div_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3537		use knuth_division_operands::{DIV_FIXED_DIVISOR, NUMERATOR};
3538
3539		let operands = (0..r).flat_map(|_| [DIV_FIXED_DIVISOR, NUMERATOR]);
3540		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3541		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3542		let (mut ext, _) = setup.ext();
3543		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3544		setup_stack(&mut interpreter, operands);
3545
3546		evict_caches();
3547		let result;
3548		#[block]
3549		{
3550			result = evm::run_plain(&mut interpreter);
3551		}
3552
3553		let ControlFlow::Break(halt) = result;
3554		assert!(matches!(halt, Halt::Stop));
3555		assert_eq!(interpreter.stack.len(), 0);
3556	}
3557
3558	/// Benchmarks `r` EVM `SDIV` op-codes.
3559	///
3560	/// # Added Overheads
3561	///
3562	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3563	/// * A carry through every limb to negate the numerator, since it's the most negative value.
3564	/// * No early exit from the check against the most negative value, since the numerator is that
3565	///   value.
3566	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3567	///   the divisor is picked at random from [`knuth_division_operands`].
3568	/// * A branch misprediction on whether the divisor and the quotient are negated, since the
3569	///   divisor's sign is pseudo-random.
3570	#[benchmark(pov_mode = Measured)]
3571	fn evm_sdiv_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3572		use knuth_division_operands::DENOMINATORS;
3573
3574		let mut rng = Pcg64::seed_from_u64(1337);
3575		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3576
3577		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3578		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3579		let (mut ext, _) = setup.ext();
3580		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3581		setup_stack(&mut interpreter, operands);
3582
3583		evict_caches();
3584		let result;
3585		#[block]
3586		{
3587			result = evm::run_plain(&mut interpreter);
3588		}
3589
3590		let ControlFlow::Break(halt) = result;
3591		assert!(matches!(halt, Halt::Stop));
3592		assert_eq!(interpreter.stack.len(), 0);
3593	}
3594
3595	/// Benchmarks `r` EVM `SDIV` op-codes with pseudo-random one-limb correction patterns.
3596	#[benchmark(pov_mode = Measured)]
3597	fn evm_sdiv_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3598		use one_limb_division_operands::DENOMINATORS;
3599
3600		let mut rng = Pcg64::seed_from_u64(1337);
3601		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3602
3603		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3604		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3605		let (mut ext, _) = setup.ext();
3606		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3607		setup_stack(&mut interpreter, operands);
3608
3609		evict_caches();
3610		let result;
3611		#[block]
3612		{
3613			result = evm::run_plain(&mut interpreter);
3614		}
3615
3616		let ControlFlow::Break(halt) = result;
3617		assert!(matches!(halt, Halt::Stop));
3618		assert_eq!(interpreter.stack.len(), 0);
3619	}
3620
3621	/// Benchmarks `r` EVM `SDIV` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3622	#[benchmark(pov_mode = Measured)]
3623	fn evm_sdiv_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3624		let mut rng = Pcg64::seed_from_u64(1337);
3625		let operands = (0..r).flat_map(|_| mixed_division_operands::signed_operands(&mut rng));
3626
3627		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3628		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3629		let (mut ext, _) = setup.ext();
3630		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3631		setup_stack(&mut interpreter, operands);
3632
3633		evict_caches();
3634		let result;
3635		#[block]
3636		{
3637			result = evm::run_plain(&mut interpreter);
3638		}
3639
3640		let ControlFlow::Break(halt) = result;
3641		assert!(matches!(halt, Halt::Stop));
3642		assert_eq!(interpreter.stack.len(), 0);
3643	}
3644
3645	/// Benchmarks `r` EVM `SDIV` op-codes with the selected fixed Knuth divisor.
3646	#[benchmark(pov_mode = Measured)]
3647	fn evm_sdiv_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3648		use knuth_division_operands::{SDIV_FIXED_DIVISOR, fixed_signed_operands};
3649
3650		let mut rng = Pcg64::seed_from_u64(1337);
3651		let operands = (0..r).flat_map(|_| fixed_signed_operands(&mut rng, SDIV_FIXED_DIVISOR));
3652
3653		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3654		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3655		let (mut ext, _) = setup.ext();
3656		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3657		setup_stack(&mut interpreter, operands);
3658
3659		evict_caches();
3660		let result;
3661		#[block]
3662		{
3663			result = evm::run_plain(&mut interpreter);
3664		}
3665
3666		let ControlFlow::Break(halt) = result;
3667		assert!(matches!(halt, Halt::Stop));
3668		assert_eq!(interpreter.stack.len(), 0);
3669	}
3670
3671	/// Benchmarks `r` EVM `MOD` op-codes.
3672	///
3673	/// # Added Overheads
3674	///
3675	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3676	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3677	///   the divisor is picked at random from [`knuth_division_operands`].
3678	#[benchmark(pov_mode = Measured)]
3679	fn evm_mod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3680		use knuth_division_operands::DENOMINATORS;
3681
3682		let mut rng = Pcg64::seed_from_u64(1337);
3683		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3684
3685		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3686		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3687		let (mut ext, _) = setup.ext();
3688		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3689		setup_stack(&mut interpreter, operands);
3690
3691		evict_caches();
3692		let result;
3693		#[block]
3694		{
3695			result = evm::run_plain(&mut interpreter);
3696		}
3697
3698		let ControlFlow::Break(halt) = result;
3699		assert!(matches!(halt, Halt::Stop));
3700		assert_eq!(interpreter.stack.len(), 0);
3701	}
3702
3703	/// Benchmarks `r` EVM `MOD` op-codes with pseudo-random one-limb correction patterns.
3704	#[benchmark(pov_mode = Measured)]
3705	fn evm_mod_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3706		use one_limb_division_operands::DENOMINATORS;
3707
3708		let mut rng = Pcg64::seed_from_u64(1337);
3709		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3710
3711		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3712		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3713		let (mut ext, _) = setup.ext();
3714		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3715		setup_stack(&mut interpreter, operands);
3716
3717		evict_caches();
3718		let result;
3719		#[block]
3720		{
3721			result = evm::run_plain(&mut interpreter);
3722		}
3723
3724		let ControlFlow::Break(halt) = result;
3725		assert!(matches!(halt, Halt::Stop));
3726		assert_eq!(interpreter.stack.len(), 0);
3727	}
3728
3729	/// Benchmarks `r` EVM `MOD` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3730	#[benchmark(pov_mode = Measured)]
3731	fn evm_mod_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3732		let mut rng = Pcg64::seed_from_u64(1337);
3733		let operands = (0..r).flat_map(|_| mixed_division_operands::unsigned_operands(&mut rng));
3734
3735		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3736		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3737		let (mut ext, _) = setup.ext();
3738		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3739		setup_stack(&mut interpreter, operands);
3740
3741		evict_caches();
3742		let result;
3743		#[block]
3744		{
3745			result = evm::run_plain(&mut interpreter);
3746		}
3747
3748		let ControlFlow::Break(halt) = result;
3749		assert!(matches!(halt, Halt::Stop));
3750		assert_eq!(interpreter.stack.len(), 0);
3751	}
3752
3753	/// Benchmarks `r` EVM `MOD` op-codes with the selected fixed Knuth divisor.
3754	#[benchmark(pov_mode = Measured)]
3755	fn evm_mod_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3756		use knuth_division_operands::{MOD_FIXED_DIVISOR, NUMERATOR};
3757
3758		let operands = (0..r).flat_map(|_| [MOD_FIXED_DIVISOR, NUMERATOR]);
3759		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3760		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3761		let (mut ext, _) = setup.ext();
3762		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3763		setup_stack(&mut interpreter, operands);
3764
3765		evict_caches();
3766		let result;
3767		#[block]
3768		{
3769			result = evm::run_plain(&mut interpreter);
3770		}
3771
3772		let ControlFlow::Break(halt) = result;
3773		assert!(matches!(halt, Halt::Stop));
3774		assert_eq!(interpreter.stack.len(), 0);
3775	}
3776
3777	/// Benchmarks `r` EVM `SMOD` op-codes.
3778	///
3779	/// # Added Overheads
3780	///
3781	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3782	/// * A carry through every limb to negate the numerator, since it's the most negative value.
3783	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3784	///   the divisor is picked at random from [`knuth_division_operands`].
3785	/// * A branch misprediction on whether the divisor is negated, since its sign is pseudo-random.
3786	#[benchmark(pov_mode = Measured)]
3787	fn evm_smod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3788		use knuth_division_operands::DENOMINATORS;
3789
3790		let mut rng = Pcg64::seed_from_u64(1337);
3791		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3792
3793		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3794		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3795		let (mut ext, _) = setup.ext();
3796		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3797		setup_stack(&mut interpreter, operands);
3798
3799		evict_caches();
3800		let result;
3801		#[block]
3802		{
3803			result = evm::run_plain(&mut interpreter);
3804		}
3805
3806		let ControlFlow::Break(halt) = result;
3807		assert!(matches!(halt, Halt::Stop));
3808		assert_eq!(interpreter.stack.len(), 0);
3809	}
3810
3811	/// Benchmarks `r` EVM `SMOD` op-codes with pseudo-random one-limb correction patterns.
3812	#[benchmark(pov_mode = Measured)]
3813	fn evm_smod_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3814		use one_limb_division_operands::DENOMINATORS;
3815
3816		let mut rng = Pcg64::seed_from_u64(1337);
3817		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3818
3819		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3820		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3821		let (mut ext, _) = setup.ext();
3822		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3823		setup_stack(&mut interpreter, operands);
3824
3825		evict_caches();
3826		let result;
3827		#[block]
3828		{
3829			result = evm::run_plain(&mut interpreter);
3830		}
3831
3832		let ControlFlow::Break(halt) = result;
3833		assert!(matches!(halt, Halt::Stop));
3834		assert_eq!(interpreter.stack.len(), 0);
3835	}
3836
3837	/// Benchmarks `r` EVM `SMOD` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3838	#[benchmark(pov_mode = Measured)]
3839	fn evm_smod_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3840		let mut rng = Pcg64::seed_from_u64(1337);
3841		let operands = (0..r).flat_map(|_| mixed_division_operands::signed_operands(&mut rng));
3842
3843		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3844		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3845		let (mut ext, _) = setup.ext();
3846		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3847		setup_stack(&mut interpreter, operands);
3848
3849		evict_caches();
3850		let result;
3851		#[block]
3852		{
3853			result = evm::run_plain(&mut interpreter);
3854		}
3855
3856		let ControlFlow::Break(halt) = result;
3857		assert!(matches!(halt, Halt::Stop));
3858		assert_eq!(interpreter.stack.len(), 0);
3859	}
3860
3861	/// Benchmarks `r` EVM `SMOD` op-codes with the selected fixed Knuth divisor.
3862	#[benchmark(pov_mode = Measured)]
3863	fn evm_smod_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3864		use knuth_division_operands::{SMOD_FIXED_DIVISOR, fixed_signed_operands};
3865
3866		let mut rng = Pcg64::seed_from_u64(1337);
3867		let operands = (0..r).flat_map(|_| fixed_signed_operands(&mut rng, SMOD_FIXED_DIVISOR));
3868
3869		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3870		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3871		let (mut ext, _) = setup.ext();
3872		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3873		setup_stack(&mut interpreter, operands);
3874
3875		evict_caches();
3876		let result;
3877		#[block]
3878		{
3879			result = evm::run_plain(&mut interpreter);
3880		}
3881
3882		let ControlFlow::Break(halt) = result;
3883		assert!(matches!(halt, Halt::Stop));
3884		assert_eq!(interpreter.stack.len(), 0);
3885	}
3886
3887	/// Benchmarks `r` EVM `ADDMOD` op-codes.
3888	///
3889	/// # Added Overheads
3890	///
3891	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3892	/// * A division for each addend, since both are above the modulus.
3893	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3894	///   the modulus is picked at random from [`knuth_division_operands`].
3895	/// * A branch misprediction on whether the modulus is subtracted from the sum, since that
3896	///   depends on the modulus picked.
3897	#[benchmark(pov_mode = Measured)]
3898	fn evm_addmod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3899		use knuth_division_operands::DENOMINATORS;
3900
3901		let mut rng = Pcg64::seed_from_u64(1337);
3902		let operands = (0..r).flat_map(|_| {
3903			let [modulus, numerator] = unsigned_division_operands(&mut rng, &DENOMINATORS);
3904			[modulus, numerator, numerator]
3905		});
3906
3907		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3908		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3909		let (mut ext, _) = setup.ext();
3910		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3911		setup_stack(&mut interpreter, operands);
3912
3913		evict_caches();
3914		let result;
3915		#[block]
3916		{
3917			result = evm::run_plain(&mut interpreter);
3918		}
3919
3920		let ControlFlow::Break(halt) = result;
3921		assert!(matches!(halt, Halt::Stop));
3922		assert_eq!(interpreter.stack.len(), 0);
3923	}
3924
3925	/// Benchmarks `r` EVM `ADDMOD` op-codes with pseudo-random one-limb correction patterns.
3926	#[benchmark(pov_mode = Measured)]
3927	fn evm_addmod_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3928		use one_limb_division_operands::DENOMINATORS;
3929
3930		let mut rng = Pcg64::seed_from_u64(1337);
3931		let operands = (0..r).flat_map(|_| {
3932			let [modulus, numerator] = unsigned_division_operands(&mut rng, &DENOMINATORS);
3933			[modulus, numerator, numerator]
3934		});
3935
3936		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3937		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3938		let (mut ext, _) = setup.ext();
3939		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3940		setup_stack(&mut interpreter, operands);
3941
3942		evict_caches();
3943		let result;
3944		#[block]
3945		{
3946			result = evm::run_plain(&mut interpreter);
3947		}
3948
3949		let ControlFlow::Break(halt) = result;
3950		assert!(matches!(halt, Halt::Stop));
3951		assert_eq!(interpreter.stack.len(), 0);
3952	}
3953
3954	/// Benchmarks `r` EVM `ADDMOD` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3955	#[benchmark(pov_mode = Measured)]
3956	fn evm_addmod_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3957		let mut rng = Pcg64::seed_from_u64(1337);
3958		let operands = (0..r).flat_map(|_| {
3959			let [modulus, numerator] = mixed_division_operands::unsigned_operands(&mut rng);
3960			[modulus, numerator, numerator]
3961		});
3962
3963		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3964		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3965		let (mut ext, _) = setup.ext();
3966		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3967		setup_stack(&mut interpreter, operands);
3968
3969		evict_caches();
3970		let result;
3971		#[block]
3972		{
3973			result = evm::run_plain(&mut interpreter);
3974		}
3975
3976		let ControlFlow::Break(halt) = result;
3977		assert!(matches!(halt, Halt::Stop));
3978		assert_eq!(interpreter.stack.len(), 0);
3979	}
3980
3981	/// Benchmarks `r` EVM `ADDMOD` op-codes with the selected fixed Knuth divisor.
3982	#[benchmark(pov_mode = Measured)]
3983	fn evm_addmod_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3984		use knuth_division_operands::{ADDMOD_FIXED_DIVISOR, NUMERATOR};
3985
3986		let operands = (0..r).flat_map(|_| [ADDMOD_FIXED_DIVISOR, NUMERATOR, NUMERATOR]);
3987		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3988		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3989		let (mut ext, _) = setup.ext();
3990		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3991		setup_stack(&mut interpreter, operands);
3992
3993		evict_caches();
3994		let result;
3995		#[block]
3996		{
3997			result = evm::run_plain(&mut interpreter);
3998		}
3999
4000		let ControlFlow::Break(halt) = result;
4001		assert!(matches!(halt, Halt::Stop));
4002		assert_eq!(interpreter.stack.len(), 0);
4003	}
4004
4005	/// Benchmarks `r` EVM `MULMOD` op-codes.
4006	///
4007	/// # Added Overheads
4008	///
4009	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
4010	/// * A product that fills every limb, since the top and lowest bits of both operands are set.
4011	/// * A multiply-subtract over all divisor limbs instead of all but the top two, since the top
4012	///   bits of the modulus are clear and division requires a nonzero normalization shift.
4013	/// * A branch misprediction on how many times the division loops, since the size of the modulus
4014	///   is pseudo-random.
4015	/// * A branch misprediction on each adjustment of the modulus's reciprocal, since whether it
4016	///   happens is pseudo-random.
4017	/// * A branch misprediction on the carry steps of the multiplication, since which limbs of one
4018	///   operand are zero is pseudo-random.
4019	#[benchmark(pov_mode = Measured)]
4020	fn evm_mulmod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
4021		let top_bit = U256::one() << 255;
4022		// The division computes the reciprocal of the top two limbs of the shifted modulus from the
4023		// top limb first, and then adjusts it twice for the second limb. These say whether each
4024		// adjustment happens.
4025		let reciprocal_adjustments = |modulus: U256| {
4026			let top = (modulus << modulus.leading_zeros() as usize) >> 128;
4027			let (high, low) = (top.low_u128() >> 64, u128::from(top.low_u64()));
4028			[low > u128::MAX % high, (U256::MAX >> 64) / top < U256::from((u128::MAX - low) / high)]
4029		};
4030		let mut rng = Pcg64::seed_from_u64(1337);
4031		let operands = (0..r).flat_map(|_| {
4032			let limbs = if rng.gen_bool(0.5) { 3 } else { 4 };
4033			let adjustments = [rng.gen_bool(0.5), rng.gen_bool(0.5)];
4034			let modulus = loop {
4035				let modulus = (U256(rng.r#gen()) | top_bit) >> (2 + 64 * (4 - limbs));
4036				if reciprocal_adjustments(modulus) == adjustments {
4037					break modulus;
4038				}
4039			};
4040			let a = U256(rng.r#gen()) | top_bit | U256::one();
4041			let mut b = U256(rng.r#gen()) | top_bit | U256::one();
4042			for limb in 1..=2 {
4043				if rng.gen_bool(0.5) {
4044					b.0[limb] = 0;
4045				}
4046			}
4047			[modulus, b, a]
4048		});
4049
4050		let code = Bytecode::new_raw([MULMOD, POP].repeat(r as usize).into());
4051		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
4052		let (mut ext, _) = setup.ext();
4053		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
4054		setup_stack(&mut interpreter, operands);
4055
4056		evict_caches();
4057		let result;
4058		#[block]
4059		{
4060			result = evm::run_plain(&mut interpreter);
4061		}
4062
4063		let ControlFlow::Break(halt) = result;
4064		assert!(matches!(halt, Halt::Stop));
4065		assert_eq!(interpreter.stack.len(), 0);
4066	}
4067
4068	// Benchmark the execution of instructions.
4069	//
4070	// It benchmarks the absolute worst case by allocating a lot of memory
4071	// and then accessing it so that each instruction generates two cache misses.
4072	#[benchmark(pov_mode = Ignored)]
4073	fn instr(r: Linear<0, 10_000>) {
4074		use rand::{SeedableRng, seq::SliceRandom};
4075		use rand_pcg::Pcg64;
4076
4077		// Ideally, this needs to be bigger than the cache.
4078		const MEMORY_SIZE: u64 = sp_core::MAX_POSSIBLE_ALLOCATION as u64;
4079
4080		// This is benchmarked for x86-64.
4081		const CACHE_LINE_SIZE: u64 = 64;
4082
4083		// An 8 byte load from this misalignment will reach into the subsequent line.
4084		const MISALIGNMENT: u64 = 60;
4085
4086		// We only need one address per cache line.
4087		// -1 because we skip the first address
4088		const NUM_ADDRESSES: u64 = (MEMORY_SIZE - MISALIGNMENT) / CACHE_LINE_SIZE - 1;
4089
4090		assert!(
4091			u64::from(r) <= NUM_ADDRESSES / 2,
4092			"If we do too many iterations we run into the risk of loading from warm cache lines",
4093		);
4094
4095		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(true));
4096		let (mut ext, module) = setup.ext();
4097		let mut prepared =
4098			CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), MEMORY_SIZE as u32);
4099
4100		assert!(
4101			u64::from(prepared.aux_data_base()) & (CACHE_LINE_SIZE - 1) == 0,
4102			"aux data base must be cache aligned"
4103		);
4104
4105		// Addresses data will be located inside the aux data.
4106		let misaligned_base = u64::from(prepared.aux_data_base()) + MISALIGNMENT;
4107
4108		// Create all possible addresses and shuffle them. This makes sure
4109		// the accesses are random but no address is accessed more than once.
4110		// we skip the first address since it is our entry point
4111		let mut addresses = Vec::with_capacity(NUM_ADDRESSES as usize);
4112		for i in 1..NUM_ADDRESSES {
4113			let addr = (misaligned_base + i * CACHE_LINE_SIZE).to_le_bytes();
4114			addresses.push(addr);
4115		}
4116		let mut rng = Pcg64::seed_from_u64(1337);
4117		addresses.shuffle(&mut rng);
4118
4119		// The addresses need to be padded to be one cache line apart.
4120		let mut memory = Vec::with_capacity((NUM_ADDRESSES * CACHE_LINE_SIZE) as usize);
4121		for address in addresses {
4122			memory.extend_from_slice(&address);
4123			memory.resize(memory.len() + CACHE_LINE_SIZE as usize - address.len(), 0);
4124		}
4125
4126		// Copies `memory` to `aux_data_base + MISALIGNMENT`.
4127		// Sets `a0 = MISALIGNMENT` and `a1 = r`.
4128		prepared
4129			.setup_aux_data(memory.as_slice(), MISALIGNMENT as u32, r.into())
4130			.unwrap();
4131
4132		#[block]
4133		{
4134			prepared.call().unwrap();
4135		}
4136	}
4137
4138	#[benchmark(pov_mode = Ignored)]
4139	fn instr_empty_loop(r: Linear<0, 10_000>) {
4140		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(false));
4141		let (mut ext, module) = setup.ext();
4142		let mut prepared = CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), 0);
4143		prepared.setup_aux_data(&[], 0, r.into()).unwrap();
4144
4145		#[block]
4146		{
4147			prepared.call().unwrap();
4148		}
4149	}
4150
4151	#[benchmark(pov_mode = Measured)]
4152	fn extcodecopy(n: Linear<1_000, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
4153		// The caller contract; `CallSetup` whitelists its `AccountInfoOf`.
4154		let mut setup = CallSetup::<T>::new(VmBinaryModule::dummy());
4155		// Copy a contract other than the caller, so its `AccountInfoOf` read is counted.
4156		let target = Contract::<T>::with_index(1, VmBinaryModule::sized(n), vec![])?;
4157
4158		let (mut ext, _) = setup.ext();
4159		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
4160
4161		// Setup stack for extcodecopy instruction: [address, dest_offset, offset, size]
4162		let _ = interpreter.stack.push(U256::from(n));
4163		let _ = interpreter.stack.push(U256::from(0u32));
4164		let _ = interpreter.stack.push(U256::from(0u32));
4165		let _ = interpreter.stack.push(target.address);
4166
4167		let result;
4168		#[block]
4169		{
4170			result = instructions::host::extcodecopy(&mut interpreter);
4171		}
4172
4173		assert!(result.is_continue());
4174		assert_eq!(
4175			*interpreter.memory.slice(0..n as usize),
4176			PristineCode::<T>::get(target.info()?.code_hash).unwrap()[0..n as usize],
4177			"Memory should contain the target contract's code after extcodecopy"
4178		);
4179
4180		Ok(())
4181	}
4182
4183	#[benchmark]
4184	fn v1_migration_step() {
4185		use crate::migrations::v1;
4186		let addr = H160::from([1u8; 20]);
4187		let contract_info = ContractInfo::new(&addr, 1u32.into(), Default::default()).unwrap();
4188
4189		v1::old::ContractInfoOf::<T>::insert(addr, contract_info.clone());
4190		let mut meter = WeightMeter::new();
4191		assert_eq!(AccountInfo::<T>::load_contract(&addr), None);
4192
4193		#[block]
4194		{
4195			v1::Migration::<T>::step(None, &mut meter).unwrap();
4196		}
4197
4198		assert_eq!(v1::old::ContractInfoOf::<T>::get(&addr), None);
4199		assert_eq!(AccountInfo::<T>::load_contract(&addr).unwrap(), contract_info);
4200
4201		// uses twice the weight once for migration and then for checking if there is another key.
4202		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v1_migration_step() * 2);
4203	}
4204
4205	#[benchmark]
4206	fn v2_migration_step() {
4207		use crate::migrations::v2;
4208		let code_hash = H256::from([0; 32]);
4209		let old_code_info = v2::Migration::<T>::create_old_code_info(
4210			whitelisted_caller(),
4211			1000u32.into(),
4212			1,
4213			100,
4214			0,
4215		);
4216		v2::Migration::<T>::insert_old_code_info(code_hash, old_code_info.clone());
4217		let mut meter = WeightMeter::new();
4218
4219		#[block]
4220		{
4221			v2::Migration::<T>::step(None, &mut meter).unwrap();
4222		}
4223
4224		v2::Migration::<T>::assert_migrated_code_info(code_hash, &old_code_info);
4225
4226		// uses twice the weight once for migration and then for checking if there is another key.
4227		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v2_migration_step() * 2);
4228	}
4229
4230	#[benchmark]
4231	fn v3_migration_step() {
4232		use crate::migrations::v3;
4233		// Remove all pre-existing accounts
4234		let _ = frame_system::Account::<T>::clear(u32::MAX, None);
4235
4236		let account = account::<T::AccountId>("target", 0, 0);
4237		T::Currency::mint_into(&account, Pallet::<T>::min_balance())
4238			.expect("should mint into account");
4239
4240		// clear the mapping so the migration has work to do
4241		let addr = T::AddressMapper::to_address(&account);
4242		crate::OriginalAccount::<T>::remove(addr);
4243
4244		assert!(!T::AddressMapper::is_mapped(&account));
4245		let mut meter = WeightMeter::new();
4246
4247		#[block]
4248		{
4249			v3::Migration::<T>::step(None, &mut meter).unwrap();
4250		}
4251
4252		assert!(T::AddressMapper::is_mapped(&account));
4253
4254		// uses twice the weight: once for migration and then for checking if there is another key.
4255		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v3_migration_step() * 2);
4256	}
4257
4258	/// One iteration of v4 phase 1: credit the uploader's [`NativeDepositOf`] bucket.
4259	///
4260	/// Seeds two codes and primes the cursor with the first stored entry so the benched
4261	/// iteration exercises the `iter_from` path that dominates phase 1 in production.
4262	#[benchmark]
4263	fn v4_code_upload_step() {
4264		use crate::migrations::v4;
4265
4266		let _ = CodeInfoOf::<T>::clear(u32::MAX, None);
4267
4268		let owner: T::AccountId = whitelisted_caller();
4269		let deposit: BalanceOf<T> = 1_000u32.into();
4270
4271		let pallet_account = Pallet::<T>::account_id();
4272		T::Currency::mint_into(&pallet_account, Pallet::<T>::min_balance()).unwrap();
4273		T::Currency::mint_into(&pallet_account, deposit).unwrap();
4274		T::Currency::hold(&HoldReason::CodeUploadDepositReserve.into(), &pallet_account, deposit)
4275			.unwrap();
4276
4277		CodeInfoOf::<T>::insert(
4278			H256::from([1u8; 32]),
4279			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
4280		);
4281		CodeInfoOf::<T>::insert(
4282			H256::from([2u8; 32]),
4283			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
4284		);
4285
4286		let first = match v4::Migration::<T>::step_once(None) {
4287			Some(v4::Cursor::CodeUpload(h)) => h,
4288			other => panic!("expected CodeUpload cursor, got {other:?}"),
4289		};
4290		let cursor = Some(v4::Cursor::CodeUpload(first));
4291
4292		#[block]
4293		{
4294			let _ = v4::Migration::<T>::step_once(cursor);
4295		}
4296
4297		assert_eq!(
4298			NativeDepositOf::<T>::get(&pallet_account, &owner),
4299			deposit + deposit,
4300			"both code uploads credited to owner",
4301		);
4302	}
4303
4304	/// One iteration of v4 phase 2: burn native hold, mint and hold PGAS for a single contract.
4305	///
4306	/// Seeds two contracts and primes the cursor with the first stored entry so the benched
4307	/// iteration exercises the `iter_from` path that dominates phase 2 in production.
4308	#[benchmark]
4309	fn v4_contract_step() {
4310		use crate::migrations::v4;
4311
4312		let _ = AccountInfoOf::<T>::clear(u32::MAX, None);
4313
4314		let code_hash = H256::from([0u8; 32]);
4315		let deposit: BalanceOf<T> = 1_000u32.into();
4316
4317		for byte in [0x41u8, 0x42u8] {
4318			let addr = H160::from([byte; 20]);
4319			let contract_account = T::AddressMapper::to_account_id(&addr);
4320			let info =
4321				ContractInfo::<T>::new(&addr, 1u32.into(), code_hash).expect("fresh contract info");
4322			AccountInfoOf::<T>::insert(
4323				addr,
4324				crate::storage::AccountInfo::<T> {
4325					account_type: crate::storage::AccountType::Contract(info),
4326					dust: 0,
4327				},
4328			);
4329			T::Currency::mint_into(&contract_account, Pallet::<T>::min_balance()).unwrap();
4330			T::Currency::mint_into(&contract_account, deposit).unwrap();
4331			T::Currency::hold(
4332				&HoldReason::StorageDepositReserve.into(),
4333				&contract_account,
4334				deposit,
4335			)
4336			.unwrap();
4337		}
4338
4339		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::Contract(None))) {
4340			Some(v4::Cursor::Contract(Some(addr))) => addr,
4341			other => panic!("expected Contract cursor, got {other:?}"),
4342		};
4343		let cursor = Some(v4::Cursor::Contract(Some(first)));
4344
4345		#[block]
4346		{
4347			let _ = v4::Migration::<T>::step_once(cursor);
4348		}
4349
4350		// `migrate_native_to_pgas` is a no-op for `Deposit = ()`, so the hold only clears on
4351		// PGAS-backed runtimes. On non-PGAS runtimes the benchmark still measures the iter cost.
4352		if T::Deposit::SUPPORTS_PGAS {
4353			for byte in [0x41u8, 0x42u8] {
4354				let addr = H160::from([byte; 20]);
4355				let contract_account = T::AddressMapper::to_account_id(&addr);
4356				assert_eq!(
4357					T::Currency::balance_on_hold(
4358						&HoldReason::StorageDepositReserve.into(),
4359						&contract_account,
4360					),
4361					0u32.into(),
4362					"native storage deposit burned for {addr:?}",
4363				);
4364			}
4365		}
4366	}
4367
4368	/// One iteration of v4 phase 3: rewrite a legacy [`v4::old::DeletionQueue`] entry into the
4369	/// new [`DeletionQueue`] format.
4370	///
4371	/// Seeds two legacy entries and primes the cursor with the first stored entry so the benched
4372	/// iteration exercises the `iter_from` path.
4373	#[benchmark]
4374	fn v4_deletion_queue_step() {
4375		use crate::migrations::v4;
4376
4377		let _ = v4::old::DeletionQueue::<T>::clear(u32::MAX, None);
4378
4379		let trie_a: TrieId = vec![0xAAu8; 16].try_into().unwrap();
4380		let trie_b: TrieId = vec![0xBBu8; 24].try_into().unwrap();
4381		v4::old::DeletionQueue::<T>::insert(0u32, trie_a);
4382		v4::old::DeletionQueue::<T>::insert(1u32, trie_b);
4383
4384		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::DeletionQueue(None))) {
4385			Some(v4::Cursor::DeletionQueue(Some(key))) => key,
4386			other => panic!("expected DeletionQueue cursor, got {other:?}"),
4387		};
4388		let cursor = Some(v4::Cursor::DeletionQueue(Some(first)));
4389
4390		#[block]
4391		{
4392			let _ = v4::Migration::<T>::step_once(cursor);
4393		}
4394
4395		assert!(
4396			DeletionQueue::<T>::get(0u32).is_some() && DeletionQueue::<T>::get(1u32).is_some(),
4397			"both legacy entries rewritten into the new format",
4398		);
4399	}
4400
4401	/// Helper function to create a test signer for finalize_block benchmark
4402	fn create_test_signer<T: Config>() -> (T::AccountId, SigningKey, H160) {
4403		use hex_literal::hex;
4404		// dev::alith()
4405		let signer_account_id = hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac");
4406		let signer_priv_key =
4407			hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133");
4408
4409		let signer_key = SigningKey::from_bytes(&signer_priv_key.into()).expect("valid key");
4410
4411		let signer_address = H160::from_slice(&signer_account_id);
4412		let signer_caller = T::AddressMapper::to_fallback_account_id(&signer_address);
4413
4414		(signer_caller, signer_key, signer_address)
4415	}
4416
4417	/// Helper function to create and sign a transaction for finalize_block benchmark
4418	fn create_signed_transaction<T: Config>(
4419		signer_key: &SigningKey,
4420		target_address: H160,
4421		value: U256,
4422		input_data: Vec<u8>,
4423	) -> Vec<u8> {
4424		let unsigned_tx: TransactionUnsigned = TransactionLegacyUnsigned {
4425			to: Some(target_address),
4426			value,
4427			chain_id: Some(T::ChainId::get().into()),
4428			input: input_data.into(),
4429			..Default::default()
4430		}
4431		.into();
4432
4433		let hashed_payload = sp_io::hashing::keccak_256(&unsigned_tx.unsigned_payload());
4434		let (signature, recovery_id) =
4435			signer_key.sign_prehash_recoverable(&hashed_payload).expect("signing success");
4436
4437		let mut sig_bytes = [0u8; 65];
4438		sig_bytes[..64].copy_from_slice(&signature.to_bytes());
4439		sig_bytes[64] = recovery_id.to_byte();
4440
4441		let signed_tx = unsigned_tx.with_signature(sig_bytes);
4442
4443		signed_tx.signed_payload()
4444	}
4445
4446	/// Helper function to generate common finalize_block benchmark setup
4447	fn setup_finalize_block_benchmark<T>()
4448	-> Result<(Contract<T>, BalanceOf<T>, U256, SigningKey, BlockNumberFor<T>), BenchmarkError>
4449	where
4450		BalanceOf<T>: Into<U256> + TryFrom<U256>,
4451		T: Config,
4452		MomentOf<T>: Into<U256>,
4453		<T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
4454	{
4455		// Setup test signer
4456		let (signer_caller, signer_key, _signer_address) = create_test_signer::<T>();
4457		whitelist_account!(signer_caller);
4458
4459		// Setup contract instance
4460		let instance =
4461			Contract::<T>::with_caller(signer_caller.clone(), VmBinaryModule::dummy(), vec![])?;
4462		let storage_deposit = default_deposit_limit::<T>();
4463		let value = Pallet::<T>::min_balance();
4464		let evm_value =
4465			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, 0));
4466
4467		// Setup block
4468		let current_block = BlockNumberFor::<T>::from(1u32);
4469		frame_system::Pallet::<T>::set_block_number(current_block);
4470
4471		// Where `Deposit` mints through `fungibles` and the runtime mirrors balance changes as
4472		// logs, creating the contract above buffers one. Drop it, so these benchmarks measure the
4473		// transactions and logs they set up themselves and no synthetic transaction on top.
4474		Pallet::<T>::clear_outside_frame_logs();
4475
4476		Ok((instance, storage_deposit, evm_value, signer_key, current_block))
4477	}
4478
4479	/// Benchmark the `on_finalize` hook scaling with number of transactions.
4480	///
4481	/// This benchmark measures the marginal computational cost of adding transactions
4482	/// to a block during finalization, with fixed payload size to isolate transaction
4483	/// count scaling effects.
4484	///
4485	/// ## Parameters:
4486	/// - `n`: Number of transactions in the block (0-200)
4487	///
4488	/// ## Test Setup:
4489	/// - Creates `n` transactions with fixed 100-byte payloads
4490	/// - Pre-populates block builder storage with test data
4491	///
4492	/// ## Usage:
4493	/// Use this with `on_finalize_per_byte` to calculate total cost:
4494	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
4495	#[benchmark(pov_mode = Measured)]
4496	fn on_finalize_per_transaction(n: Linear<0, 200>) -> Result<(), BenchmarkError> {
4497		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4498			setup_finalize_block_benchmark::<T>()?;
4499
4500		// Fixed payload size to isolate transaction count effects
4501		let fixed_payload_size = 100usize;
4502
4503		// Pre-populate InflightTransactions with n transactions of fixed size
4504		if n > 0 {
4505			// Initialize block
4506			let _ = Pallet::<T>::on_initialize(current_block);
4507
4508			// Create input data of fixed size for consistent transaction payloads
4509			let input_data = vec![0x42u8; fixed_payload_size];
4510			let receipt_gas_info = ReceiptGasInfo {
4511				gas_used: U256::from(1_000_000),
4512				effective_gas_price: Pallet::<T>::evm_base_fee(),
4513			};
4514
4515			for _ in 0..n {
4516				// Create real signed transaction with fixed-size input data
4517				let signed_transaction = create_signed_transaction::<T>(
4518					&signer_key,
4519					instance.address,
4520					evm_value,
4521					input_data.clone(),
4522				);
4523
4524				// Store transaction
4525				let _ = block_storage::bench_with_ethereum_context(|| {
4526					let (encoded_logs, bloom) =
4527						block_storage::get_receipt_details::<T>().unwrap_or_default();
4528
4529					let block_builder_ir = EthBlockBuilderIR::<T>::get();
4530					let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4531
4532					block_builder.process_transaction(
4533						signed_transaction,
4534						true,
4535						receipt_gas_info.clone(),
4536						encoded_logs,
4537						bloom,
4538					);
4539
4540					EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4541				});
4542			}
4543		}
4544
4545		#[block]
4546		{
4547			// Measure only the finalization cost with n transactions of fixed size
4548			let _ = Pallet::<T>::on_finalize(current_block);
4549		}
4550
4551		// Verify transaction count
4552		assert_eq!(Pallet::<T>::eth_block().transactions.len(), n as usize);
4553
4554		Ok(())
4555	}
4556
4557	/// Benchmark the `on_finalize` hook scaling with transaction payload size.
4558	///
4559	/// This benchmark measures the marginal computational cost of processing
4560	/// larger transaction payloads during finalization, with fixed transaction count
4561	/// to isolate payload size scaling effects.
4562	///
4563	/// ## Parameters:
4564	/// - `d`: Payload size per transaction in bytes (0-1000)
4565	///
4566	/// ## Test Setup:
4567	/// - Creates 10 transactions with payload size `d`
4568	/// - Pre-populates block builder storage with test data
4569	///
4570	/// ## Usage:
4571	/// Use this with `on_finalize_per_transaction` to calculate total cost:
4572	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
4573	#[benchmark(pov_mode = Measured)]
4574	fn on_finalize_per_transaction_data(d: Linear<0, 1000>) -> Result<(), BenchmarkError> {
4575		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4576			setup_finalize_block_benchmark::<T>()?;
4577
4578		// Fixed transaction count to isolate payload size effects
4579		let fixed_tx_count = 10u32;
4580
4581		// Initialize block
4582		let _ = Pallet::<T>::on_initialize(current_block);
4583
4584		// Create input data of variable size p for realistic transaction payloads
4585		let input_data = vec![0x42u8; d as usize];
4586		let receipt_gas_info = ReceiptGasInfo {
4587			gas_used: U256::from(1_000_000),
4588			effective_gas_price: Pallet::<T>::evm_base_fee(),
4589		};
4590
4591		for _ in 0..fixed_tx_count {
4592			// Create real signed transaction with variable-size input data
4593			let signed_transaction = create_signed_transaction::<T>(
4594				&signer_key,
4595				instance.address,
4596				evm_value,
4597				input_data.clone(),
4598			);
4599
4600			// Store transaction
4601			let _ = block_storage::bench_with_ethereum_context(|| {
4602				let (encoded_logs, bloom) =
4603					block_storage::get_receipt_details::<T>().unwrap_or_default();
4604
4605				let block_builder_ir = EthBlockBuilderIR::<T>::get();
4606				let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4607
4608				block_builder.process_transaction(
4609					signed_transaction,
4610					true,
4611					receipt_gas_info.clone(),
4612					encoded_logs,
4613					bloom,
4614				);
4615
4616				EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4617			});
4618		}
4619
4620		#[block]
4621		{
4622			// Measure only the finalization cost with fixed count, variable payload size
4623			let _ = Pallet::<T>::on_finalize(current_block);
4624		}
4625
4626		// Verify transaction count
4627		assert_eq!(Pallet::<T>::eth_block().transactions.len(), fixed_tx_count as usize);
4628
4629		Ok(())
4630	}
4631
4632	/// Benchmark the `on_finalize` per-event costs.
4633	///
4634	/// This benchmark measures the computational cost of processing events
4635	/// within the finalization process, isolating the overhead of event count.
4636	/// Uses a single transaction with varying numbers of minimal events.
4637	///
4638	/// ## Parameters:
4639	/// - `e`: Number of events per transaction
4640	///
4641	/// ## Test Setup:
4642	/// - Creates 1 transaction with `e` ContractEmitted events
4643	/// - Each event contains minimal data (no topics, empty data field)
4644	///
4645	/// ## Usage:
4646	/// Measures the per-event processing overhead during finalization
4647	/// - Fixed cost: `on_finalize_per_event(0)` - baseline finalization cost
4648	/// - Per event: `on_finalize_per_event(e)` - linear scaling with event count
4649	#[benchmark(pov_mode = Measured)]
4650	fn on_finalize_per_event(e: Linear<0, 100>) -> Result<(), BenchmarkError> {
4651		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4652			setup_finalize_block_benchmark::<T>()?;
4653
4654		// Create a single transaction with e events, each with minimal data
4655		let input_data = vec![0x42u8; 100];
4656		let signed_transaction = create_signed_transaction::<T>(
4657			&signer_key,
4658			instance.address,
4659			evm_value,
4660			input_data.clone(),
4661		);
4662
4663		let receipt_gas_info = ReceiptGasInfo {
4664			gas_used: U256::from(1_000_000),
4665			effective_gas_price: Pallet::<T>::evm_base_fee(),
4666		};
4667
4668		// Store transaction
4669		let _ = block_storage::bench_with_ethereum_context(|| {
4670			// Captured inside the ethereum context, so each lands on the transaction's own receipt
4671			// — the path `on_finalize_block_per_event` is charged for on every `DepositEvent`. The
4672			// outside-of-frame drain is `outside_frame_log`'s, charged separately.
4673			for _ in 0..e {
4674				block_storage::capture_frame_log::<T>(&instance.address, &vec![], &vec![]);
4675			}
4676
4677			let (encoded_logs, bloom) =
4678				block_storage::get_receipt_details::<T>().unwrap_or_default();
4679
4680			let block_builder_ir = EthBlockBuilderIR::<T>::get();
4681			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4682
4683			block_builder.process_transaction(
4684				signed_transaction,
4685				true,
4686				receipt_gas_info.clone(),
4687				encoded_logs,
4688				bloom,
4689			);
4690
4691			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4692		});
4693
4694		#[block]
4695		{
4696			// Initialize block
4697			let _ = Pallet::<T>::on_initialize(current_block);
4698
4699			// Measure the finalization cost with e events
4700			let _ = Pallet::<T>::on_finalize(current_block);
4701		}
4702
4703		// The real transaction only: its `e` logs are on its own receipt, so no synthetic
4704		// transaction is built.
4705		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
4706
4707		Ok(())
4708	}
4709
4710	/// ## Test Setup:
4711	/// - Creates 1 transaction with 1 ContractEmitted event
4712	/// - Event contains `d` total bytes of data across data field and topics
4713	///
4714	/// ## Usage:
4715	/// Measures the per-byte event data processing overhead during finalization
4716	/// - Fixed cost: `on_finalize_per_event_data(0)` - baseline cost with empty event
4717	/// - Per byte: `on_finalize_per_event_data(d)` - linear scaling with data size
4718	#[benchmark(pov_mode = Measured)]
4719	fn on_finalize_per_event_data(d: Linear<0, 16384>) -> Result<(), BenchmarkError> {
4720		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4721			setup_finalize_block_benchmark::<T>()?;
4722
4723		// Create a single transaction with one event containing d bytes of data
4724		let input_data = vec![0x42u8; 100];
4725		let signed_transaction = create_signed_transaction::<T>(
4726			&signer_key,
4727			instance.address,
4728			evm_value,
4729			input_data.clone(),
4730		);
4731
4732		let receipt_gas_info = ReceiptGasInfo {
4733			gas_used: U256::from(1_000_000),
4734			effective_gas_price: Pallet::<T>::evm_base_fee(),
4735		};
4736
4737		// Create one event with d bytes of data distributed across topics and data field
4738		let (event_data, topics) = if d < 32 {
4739			// If total data is less than 32 bytes, put all in data field
4740			(vec![0x42u8; d as usize], vec![])
4741		} else {
4742			// Fill topics first, then put remaining bytes in data field
4743			let num_topics = core::cmp::min(limits::NUM_EVENT_TOPICS, d / 32);
4744			let topic_bytes_used = num_topics * 32;
4745			let data_bytes_remaining = d - topic_bytes_used;
4746
4747			// Create topics filled with sequential data
4748			let mut topics = Vec::new();
4749			for topic_index in 0..num_topics {
4750				let topic_data = [topic_index as u8; 32];
4751				topics.push(H256::from(topic_data));
4752			}
4753
4754			// Remaining bytes go to data field
4755			let event_data = vec![0x42u8; data_bytes_remaining as usize];
4756
4757			(event_data, topics)
4758		};
4759
4760		// Store transaction
4761		let _ = block_storage::bench_with_ethereum_context(|| {
4762			// Captured inside the ethereum context, so the log lands on the transaction's own
4763			// receipt — the path `on_finalize_block_per_event` is charged for on every
4764			// `DepositEvent`. The outside-of-frame drain is `outside_frame_log`'s, charged
4765			// separately.
4766			block_storage::capture_frame_log::<T>(&instance.address, &event_data, &topics);
4767
4768			let (encoded_logs, bloom) =
4769				block_storage::get_receipt_details::<T>().unwrap_or_default();
4770
4771			let block_builder_ir = EthBlockBuilderIR::<T>::get();
4772			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4773
4774			block_builder.process_transaction(
4775				signed_transaction,
4776				true,
4777				receipt_gas_info,
4778				encoded_logs,
4779				bloom,
4780			);
4781
4782			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4783		});
4784
4785		#[block]
4786		{
4787			// Initialize block
4788			let _ = Pallet::<T>::on_initialize(current_block);
4789
4790			// Measure the finalization cost with d bytes of event data
4791			let _ = Pallet::<T>::on_finalize(current_block);
4792		}
4793
4794		// The real transaction only: its log is on its own receipt, so no synthetic transaction is
4795		// built.
4796		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
4797
4798		Ok(())
4799	}
4800
4801	/// Benchmark the `on_finalize` drain of `n` buffered outside-of-frame logs: taking the buffer
4802	/// and folding each log into the synthetic transaction's receipt (RLP + bloom).
4803	///
4804	/// `pov_mode = Measured` because the block builder's storage is unbounded. The buffer itself is
4805	/// whitelisted, so the marginal carries no proof size; a log's proof charge is its entry's
4806	/// bytes, registered where it is buffered. The append is in the emitting pallet's weight, see
4807	/// `buffer_outside_frame_log`.
4808	///
4809	/// Each log uses a representative ERC-20 `Transfer` payload: three 32-byte topics and a 32-byte
4810	/// data word.
4811	#[benchmark(pov_mode = Measured)]
4812	fn outside_frame_log(n: Linear<0, 100>) -> Result<(), BenchmarkError> {
4813		let (instance, _storage_deposit, _evm_value, _signer_key, current_block) =
4814			setup_finalize_block_benchmark::<T>()?;
4815
4816		// Realistic order: `on_initialize` runs before the block's extrinsics buffer any logs.
4817		let _ = Pallet::<T>::on_initialize(current_block);
4818
4819		// Representative ERC-20 `Transfer`: topics = [event sig, from, to], data = value word.
4820		let topics =
4821			vec![H256::repeat_byte(0x11), H256::repeat_byte(0x22), H256::repeat_byte(0x33)];
4822		let data = vec![0x44u8; 32];
4823
4824		// Appended straight into the buffer, past the cap: the drain is what is measured, and a
4825		// runtime that keeps the buffer off still needs its weight.
4826		for _ in 0..n {
4827			Pallet::<T>::bench_buffer_outside_frame_log(
4828				instance.address,
4829				topics.clone(),
4830				data.clone(),
4831			);
4832		}
4833
4834		#[block]
4835		{
4836			let _ = Pallet::<T>::on_finalize(current_block);
4837		}
4838
4839		// Only the synthetic transaction, and only when at least one log was buffered.
4840		assert_eq!(Pallet::<T>::eth_block().transactions.len(), (n > 0) as usize);
4841
4842		Ok(())
4843	}
4844
4845	/// Benchmark the `on_finalize` drain of one buffered outside-of-frame log carrying `d` bytes
4846	/// of data: the `OutsideFrameLogs::take` scales with the value it reads back, and so do the
4847	/// RLP encoding and bloom accrual that fold it into the synthetic transaction's receipt.
4848	///
4849	/// Pairs with `outside_frame_log`, which fixes the payload and varies the count; together they
4850	/// give `OnFinalizeBlockParts::per_outside_frame_log` its per-log and per-byte marginals. A
4851	/// contract `LOG` emitted off the ethereum path can carry up to `EVENT_BYTES`, so the count
4852	/// benchmark's 32-byte word alone would under-charge it.
4853	#[benchmark(pov_mode = Measured)]
4854	fn outside_frame_log_data(d: Linear<0, { limits::EVENT_BYTES }>) -> Result<(), BenchmarkError> {
4855		let (instance, _storage_deposit, _evm_value, _signer_key, current_block) =
4856			setup_finalize_block_benchmark::<T>()?;
4857
4858		let _ = Pallet::<T>::on_initialize(current_block);
4859
4860		let topics =
4861			vec![H256::repeat_byte(0x11), H256::repeat_byte(0x22), H256::repeat_byte(0x33)];
4862		let data = vec![0x44u8; d as usize];
4863
4864		Pallet::<T>::bench_buffer_outside_frame_log(instance.address, topics, data);
4865
4866		#[block]
4867		{
4868			let _ = Pallet::<T>::on_finalize(current_block);
4869		}
4870
4871		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
4872
4873		Ok(())
4874	}
4875
4876	impl_benchmark_test_suite!(
4877		Contracts,
4878		crate::tests::ExtBuilder::default().build(),
4879		crate::tests::Test,
4880	);
4881}