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	#[benchmark(pov_mode = Measured)]
1732	fn seal_terminate_logic() -> Result<(), BenchmarkError> {
1733		let caller = whitelisted_caller();
1734		let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
1735		T::AddressMapper::map_no_deposit_unchecked(&beneficiary)?;
1736
1737		build_runtime!(_runtime, instance, _memory: [vec![0u8; 0], ]);
1738		let code_hash = instance.info()?.code_hash;
1739
1740		assert!(PristineCode::<T>::get(code_hash).is_some());
1741
1742		T::Currency::set_balance(&instance.account_id, Pallet::<T>::min_balance() * 10u32.into());
1743
1744		let storage_deposit = T::Currency::balance_on_hold(
1745			&HoldReason::StorageDepositReserve.into(),
1746			&instance.account_id,
1747		);
1748		NativeDepositOf::<T>::insert(&instance.account_id, &caller, storage_deposit);
1749
1750		let mut transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit {
1751			weight_limit: Default::default(),
1752			deposit_limit: BalanceOf::<T>::max_value(),
1753		})
1754		.unwrap();
1755		let exec_config = ExecConfig::new_substrate_tx();
1756		let contract_account = &instance.account_id;
1757		let origin = &ExecOrigin::from_account_id(caller);
1758		let beneficiary_clone = beneficiary.clone();
1759		let trie_id = instance.info()?.trie_id.clone();
1760		let code_hash = instance.info()?.code_hash;
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				trie_id,
1773				code_hash,
1774				only_if_same_tx,
1775			);
1776		}
1777		result.unwrap();
1778
1779		// Check that the contract is removed
1780		assert!(PristineCode::<T>::get(code_hash).is_none());
1781
1782		// Check that the balance has been transferred away
1783		let balance = <T as Config>::Currency::total_balance(&instance.account_id);
1784		assert_eq!(balance, 0u32.into());
1785
1786		// Check that the beneficiary received the balance
1787		let balance = <T as Config>::Currency::balance(&beneficiary);
1788		assert_eq!(balance, Pallet::<T>::min_balance() + Pallet::<T>::min_balance() * 9u32.into());
1789
1790		Ok(())
1791	}
1792
1793	// Benchmark the overhead that topics generate.
1794	// `t`: Number of topics
1795	// `n`: Size of event payload in bytes
1796	#[benchmark(pov_mode = Measured)]
1797	fn seal_deposit_event(
1798		t: Linear<0, { limits::NUM_EVENT_TOPICS as u32 }>,
1799		n: Linear<0, { limits::EVENT_BYTES }>,
1800	) {
1801		let num_topic = t as u32;
1802		let topics = (0..t).map(|i| H256::repeat_byte(i as u8)).collect::<Vec<_>>();
1803		let topics_data =
1804			topics.iter().flat_map(|hash| hash.as_bytes().to_vec()).collect::<Vec<u8>>();
1805		let data = vec![42u8; n as _];
1806		build_runtime!(runtime, instance, memory: [ topics_data, data, ]);
1807
1808		// Inside an ethereum transaction, the worst case: the log is also captured into the open
1809		// receipt, which encodes it, accrues the bloom and writes what the transaction has
1810		// committed so far to storage.
1811		let result;
1812		#[block]
1813		{
1814			result = block_storage::bench_with_ethereum_context(|| {
1815				runtime.bench_deposit_event(
1816					memory.as_mut_slice(),
1817					0, // topics_ptr
1818					num_topic,
1819					topics_data.len() as u32, // data_ptr
1820					n,                        // data_len
1821				)
1822			});
1823		}
1824		assert_ok!(result);
1825
1826		let events = System::<T>::events();
1827		let record = &events[events.len() - 1];
1828
1829		assert_eq!(
1830			record.event,
1831			crate::Event::ContractEmitted { contract: instance.address, data, topics }.into(),
1832		);
1833	}
1834
1835	enum TrieFill {
1836		Empty,
1837		Full,
1838	}
1839
1840	enum SlotAccess {
1841		Cold,
1842		Hot,
1843	}
1844
1845	fn build_storage_contract<T: Config>(
1846		op: StorageOp,
1847		fill: TrieFill,
1848	) -> Result<(ContractInfo<T>, Vec<u8>, Vec<u8>), BenchmarkError> {
1849		let key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1850		let value = vec![1u8; limits::STORAGE_BYTES as usize];
1851		let initial_value = match op {
1852			StorageOp::Read => value.clone(),
1853			StorageOp::Write => vec![42u8; limits::STORAGE_BYTES as usize],
1854		};
1855
1856		let instance = match fill {
1857			TrieFill::Full => {
1858				Contract::<T>::with_unbalanced_storage_trie(VmBinaryModule::dummy(), &key)?
1859			},
1860			TrieFill::Empty => Contract::<T>::new(VmBinaryModule::dummy(), vec![])?,
1861		};
1862		let info = instance.info()?;
1863		info.bench_write_raw(&key, Some(initial_value), false)
1864			.map_err(|_| "Failed to write to storage during setup.")?;
1865		Ok((info, key, value))
1866	}
1867
1868	enum StorageCall {
1869		Clear,
1870		Contains,
1871		Take,
1872	}
1873
1874	fn setup_precompile_bench<T: Config>(
1875		op: StorageCall,
1876		key_byte: u8,
1877		access: SlotAccess,
1878	) -> Result<(CallSetup<T>, Key, Vec<u8>), BenchmarkError> {
1879		let max_key_len = limits::STORAGE_KEY_BYTES;
1880		let key = Key::try_from_var(vec![key_byte; max_key_len as usize])
1881			.map_err(|_| "Key has wrong length")?;
1882		let raw_key = vec![key_byte; max_key_len as usize].into();
1883		let input_bytes = match op {
1884			StorageCall::Clear => {
1885				IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
1886					flags: StorageFlags::empty().bits(),
1887					key: raw_key,
1888					isFixedKey: false,
1889				})
1890			},
1891			StorageCall::Contains => {
1892				IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
1893					flags: StorageFlags::empty().bits(),
1894					key: raw_key,
1895					isFixedKey: false,
1896				})
1897			},
1898			StorageCall::Take => IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
1899				flags: StorageFlags::empty().bits(),
1900				key: raw_key,
1901				isFixedKey: false,
1902			}),
1903		}
1904		.abi_encode();
1905
1906		let call_setup = CallSetup::<T>::default();
1907		if matches!(access, SlotAccess::Hot) {
1908			let info = call_setup.contract().info()?;
1909			frame_benchmarking::add_to_whitelist_child(
1910				info.child_trie_info().storage_key().to_vec(),
1911				key.hash(),
1912			);
1913		}
1914		Ok((call_setup, key, input_bytes))
1915	}
1916
1917	#[benchmark(skip_meta, pov_mode = Measured)]
1918	fn get_storage_empty() -> Result<(), BenchmarkError> {
1919		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Empty)?;
1920		let child_trie_info = info.child_trie_info();
1921
1922		let result;
1923		#[block]
1924		{
1925			result = child::get_raw(&child_trie_info, &key);
1926		}
1927
1928		assert_eq!(result, Some(value));
1929		Ok(())
1930	}
1931
1932	#[benchmark(skip_meta, pov_mode = Measured)]
1933	fn get_storage_full() -> Result<(), BenchmarkError> {
1934		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Full)?;
1935		let child_trie_info = info.child_trie_info();
1936
1937		let result;
1938		#[block]
1939		{
1940			result = child::get_raw(&child_trie_info, &key);
1941		}
1942
1943		assert_eq!(result, Some(value));
1944		Ok(())
1945	}
1946
1947	#[benchmark(skip_meta, pov_mode = Measured)]
1948	fn set_storage_empty() -> Result<(), BenchmarkError> {
1949		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Empty)?;
1950
1951		let val = Some(value.clone());
1952		let result;
1953		#[block]
1954		{
1955			result = info.bench_write_raw(&key, val, true);
1956		}
1957
1958		assert_ok!(result);
1959		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1960		Ok(())
1961	}
1962
1963	#[benchmark(skip_meta, pov_mode = Measured)]
1964	fn set_storage_full() -> Result<(), BenchmarkError> {
1965		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Full)?;
1966
1967		let val = Some(value.clone());
1968		let result;
1969		#[block]
1970		{
1971			result = info.bench_write_raw(&key, val, true);
1972		}
1973
1974		assert_ok!(result);
1975		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1976		Ok(())
1977	}
1978
1979	/// Worst-case lookup keys: differing only at the tail, comparisons walk the whole key.
1980	fn shared_prefix_keys(count: usize, suffix_of: impl Fn(u64) -> u64) -> Vec<Vec<u8>> {
1981		(0..count as u64)
1982			.map(|i| {
1983				let mut key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1984				key[limits::STORAGE_KEY_BYTES as usize - 8..]
1985					.copy_from_slice(&suffix_of(i).to_be_bytes());
1986				key
1987			})
1988			.collect()
1989	}
1990
1991	/// Probed keys must exist, or reads measure a proof-of-absence walk instead.
1992	/// Whitelisting keeps them out of the DB counters and pre-warms the cache.
1993	fn setup_stored_keys<T: Config>(
1994		count: usize,
1995		value_byte: u8,
1996		suffix_of: impl Fn(u64) -> u64,
1997	) -> Result<(ContractInfo<T>, Vec<Vec<u8>>), BenchmarkError> {
1998		let instance = Contract::<T>::new(VmBinaryModule::dummy(), vec![])?;
1999		let info = instance.info()?;
2000		let child_trie_info = info.child_trie_info();
2001		let value = vec![value_byte; limits::STORAGE_BYTES as usize];
2002		let stored_keys = shared_prefix_keys(count, suffix_of);
2003		for key in &stored_keys {
2004			info.bench_write_raw(key, Some(value.clone()), false)
2005				.map_err(|_| "Failed to write to storage during setup.")?;
2006			frame_benchmarking::add_to_whitelist_child(
2007				child_trie_info.storage_key().to_vec(),
2008				key.clone(),
2009			);
2010		}
2011		Ok((info, stored_keys))
2012	}
2013
2014	#[benchmark(skip_meta, pov_mode = Measured)]
2015	fn overlay_probe_full(
2016		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
2017	) -> Result<(), BenchmarkError> {
2018		let value_byte = 42;
2019		// One stored key per probe; odd suffixes, so the even fill never overwrites them.
2020		let (info, stored_keys) =
2021			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
2022		let child_trie_info = info.child_trie_info();
2023		// The block's PoV budget bounds the overlay entries like the access list cap.
2024		let fill_keys = shared_prefix_keys(MAX_ACCESS_LIST_ENTRIES, |i| (i + 1) * 2);
2025
2026		let mut result = None;
2027		#[block]
2028		{
2029			// The benchmark framework drains the overlay right before the block, so fill here.
2030			for key in &fill_keys {
2031				child::put_raw(&child_trie_info, key, &[0u8]);
2032			}
2033			for i in 0..n {
2034				let index = i as usize % stored_keys.len();
2035				result = child::get_raw(&child_trie_info, &stored_keys[index]);
2036			}
2037		}
2038
2039		if n > 0 {
2040			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
2041			assert_eq!(result, Some(expected), "the stored value must be read back");
2042		}
2043		Ok(())
2044	}
2045
2046	#[benchmark(skip_meta, pov_mode = Measured)]
2047	fn overlay_probe_empty(
2048		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
2049	) -> Result<(), BenchmarkError> {
2050		let value_byte = 42;
2051		// Reads must cost the same as in `overlay_probe_full`, so their shared cost cancels.
2052		let (info, stored_keys) =
2053			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
2054		let child_trie_info = info.child_trie_info();
2055
2056		let mut result = None;
2057		#[block]
2058		{
2059			for i in 0..n {
2060				let index = i as usize % stored_keys.len();
2061				result = child::get_raw(&child_trie_info, &stored_keys[index]);
2062			}
2063		}
2064
2065		if n > 0 {
2066			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
2067			assert_eq!(result, Some(expected), "the stored value must be read back");
2068		}
2069		Ok(())
2070	}
2071
2072	// n: new byte size
2073	// o: old byte size
2074	#[benchmark(skip_meta, pov_mode = Measured)]
2075	fn seal_set_storage(
2076		n: Linear<0, { limits::STORAGE_BYTES }>,
2077		o: Linear<0, { limits::STORAGE_BYTES }>,
2078	) -> Result<(), BenchmarkError> {
2079		let max_key_len = limits::STORAGE_KEY_BYTES;
2080		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2081			.map_err(|_| "Key has wrong length")?;
2082		let value = vec![1u8; n as usize];
2083
2084		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
2085		let info = instance.info()?;
2086
2087		info.write(&key, Some(vec![42u8; o as usize]), None, false)
2088			.map_err(|_| "Failed to write to storage during setup.")?;
2089
2090		let result;
2091		#[block]
2092		{
2093			result = runtime.bench_set_storage(
2094				memory.as_mut_slice(),
2095				StorageFlags::empty().bits(),
2096				0,           // key_ptr
2097				max_key_len, // key_len
2098				max_key_len, // value_ptr
2099				n,           // value_len
2100			);
2101		}
2102
2103		assert_ok!(result);
2104		assert_eq!(info.read(&key).unwrap(), value);
2105		Ok(())
2106	}
2107
2108	#[benchmark(skip_meta, pov_mode = Measured)]
2109	fn seal_set_storage_hot(
2110		n: Linear<0, { limits::STORAGE_BYTES }>,
2111		o: Linear<0, { limits::STORAGE_BYTES }>,
2112	) -> Result<(), BenchmarkError> {
2113		let max_key_len = limits::STORAGE_KEY_BYTES;
2114		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2115			.map_err(|_| "Key has wrong length")?;
2116		let value = vec![1u8; n as usize];
2117
2118		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
2119		let info = instance.info()?;
2120
2121		info.write(&key, Some(vec![42u8; o as usize]), None, false)
2122			.map_err(|_| "Failed to write to storage during setup.")?;
2123
2124		frame_benchmarking::add_to_whitelist_child(
2125			info.child_trie_info().storage_key().to_vec(),
2126			key.hash(),
2127		);
2128
2129		// Add the key to access list so the op's touch is hot.
2130		runtime.ext().touch_storage_access(&key, StorageOp::Write);
2131
2132		let result;
2133		#[block]
2134		{
2135			result = runtime.bench_set_storage(
2136				memory.as_mut_slice(),
2137				StorageFlags::empty().bits(),
2138				0,           // key_ptr
2139				max_key_len, // key_len
2140				max_key_len, // value_ptr
2141				n,           // value_len
2142			);
2143		}
2144
2145		assert_ok!(result);
2146		assert_eq!(info.read(&key).unwrap(), value);
2147		Ok(())
2148	}
2149
2150	#[benchmark(skip_meta, pov_mode = Measured)]
2151	fn clear_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2152		let key_byte = 0;
2153		let (mut call_setup, key, input_bytes) =
2154			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Cold)?;
2155		let (mut ext, _) = call_setup.ext();
2156		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2157			.map_err(|_| "Failed to write to storage during setup.")?;
2158
2159		let result;
2160		#[block]
2161		{
2162			result = run_builtin_precompile(
2163				&mut ext,
2164				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2165				input_bytes,
2166			);
2167		}
2168		assert_ok!(result);
2169		assert!(ext.get_storage(&key).is_none());
2170
2171		Ok(())
2172	}
2173
2174	#[benchmark(skip_meta, pov_mode = Measured)]
2175	fn clear_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2176		let key_byte = 0;
2177		let (mut call_setup, key, input_bytes) =
2178			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Hot)?;
2179		let (mut ext, _) = call_setup.ext();
2180		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2181			.map_err(|_| "Failed to write to storage during setup.")?;
2182
2183		ext.touch_storage_access(&key, StorageOp::Write);
2184
2185		let result;
2186		#[block]
2187		{
2188			result = run_builtin_precompile(
2189				&mut ext,
2190				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2191				input_bytes,
2192			);
2193		}
2194		assert_ok!(result);
2195		assert!(ext.get_storage(&key).is_none());
2196
2197		Ok(())
2198	}
2199
2200	#[benchmark(skip_meta, pov_mode = Measured)]
2201	fn seal_get_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2202		let max_key_len = limits::STORAGE_KEY_BYTES;
2203		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2204			.map_err(|_| "Key has wrong length")?;
2205		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2206		let info = instance.info()?;
2207
2208		info.write(&key, Some(vec![42u8; n as usize]), None, false)
2209			.map_err(|_| "Failed to write to storage during setup.")?;
2210
2211		let out_ptr = max_key_len + 4;
2212		let result;
2213		#[block]
2214		{
2215			result = runtime.bench_get_storage(
2216				memory.as_mut_slice(),
2217				StorageFlags::empty().bits(),
2218				0,           // key_ptr
2219				max_key_len, // key_len
2220				out_ptr,     // out_ptr
2221				max_key_len, // out_len_ptr
2222			);
2223		}
2224
2225		assert_ok!(result);
2226		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
2227		Ok(())
2228	}
2229
2230	#[benchmark(skip_meta, pov_mode = Measured)]
2231	fn seal_get_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2232		let max_key_len = limits::STORAGE_KEY_BYTES;
2233		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2234			.map_err(|_| "Key has wrong length")?;
2235		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2236		let info = instance.info()?;
2237
2238		info.write(&key, Some(vec![42u8; n as usize]), None, false)
2239			.map_err(|_| "Failed to write to storage during setup.")?;
2240
2241		frame_benchmarking::add_to_whitelist_child(
2242			info.child_trie_info().storage_key().to_vec(),
2243			key.hash(),
2244		);
2245
2246		runtime.ext().touch_storage_access(&key, StorageOp::Read);
2247
2248		let out_ptr = max_key_len + 4;
2249		let result;
2250		#[block]
2251		{
2252			result = runtime.bench_get_storage(
2253				memory.as_mut_slice(),
2254				StorageFlags::empty().bits(),
2255				0,           // key_ptr
2256				max_key_len, // key_len
2257				out_ptr,     // out_ptr
2258				max_key_len, // out_len_ptr
2259			);
2260		}
2261
2262		assert_ok!(result);
2263		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
2264		Ok(())
2265	}
2266
2267	#[benchmark(skip_meta, pov_mode = Measured)]
2268	fn contains_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2269		let key_byte = 0;
2270		let (mut call_setup, key, input_bytes) =
2271			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Cold)?;
2272		let (mut ext, _) = call_setup.ext();
2273		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2274			.map_err(|_| "Failed to write to storage during setup.")?;
2275
2276		let result;
2277		#[block]
2278		{
2279			result = run_builtin_precompile(
2280				&mut ext,
2281				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2282				input_bytes,
2283			);
2284		}
2285		assert_ok!(result);
2286		assert!(ext.get_storage(&key).is_some());
2287
2288		Ok(())
2289	}
2290
2291	#[benchmark(skip_meta, pov_mode = Measured)]
2292	fn contains_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2293		let key_byte = 0;
2294		let (mut call_setup, key, input_bytes) =
2295			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Hot)?;
2296		let (mut ext, _) = call_setup.ext();
2297		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2298			.map_err(|_| "Failed to write to storage during setup.")?;
2299
2300		ext.touch_storage_access(&key, StorageOp::Read);
2301
2302		let result;
2303		#[block]
2304		{
2305			result = run_builtin_precompile(
2306				&mut ext,
2307				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2308				input_bytes,
2309			);
2310		}
2311		assert_ok!(result);
2312		assert!(ext.get_storage(&key).is_some());
2313
2314		Ok(())
2315	}
2316
2317	#[benchmark(skip_meta, pov_mode = Measured)]
2318	fn take_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2319		let key_byte = 3;
2320		let (mut call_setup, key, input_bytes) =
2321			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Cold)?;
2322		let (mut ext, _) = call_setup.ext();
2323		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2324			.map_err(|_| "Failed to write to storage during setup.")?;
2325
2326		let result;
2327		#[block]
2328		{
2329			result = run_builtin_precompile(
2330				&mut ext,
2331				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2332				input_bytes,
2333			);
2334		}
2335		assert_ok!(result);
2336		assert!(ext.get_storage(&key).is_none());
2337
2338		Ok(())
2339	}
2340
2341	#[benchmark(skip_meta, pov_mode = Measured)]
2342	fn take_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
2343		let key_byte = 3;
2344		let (mut call_setup, key, input_bytes) =
2345			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Hot)?;
2346		let (mut ext, _) = call_setup.ext();
2347		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
2348			.map_err(|_| "Failed to write to storage during setup.")?;
2349
2350		ext.touch_storage_access(&key, StorageOp::Write);
2351
2352		let result;
2353		#[block]
2354		{
2355			result = run_builtin_precompile(
2356				&mut ext,
2357				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2358				input_bytes,
2359			);
2360		}
2361		assert_ok!(result);
2362		assert!(ext.get_storage(&key).is_none());
2363
2364		Ok(())
2365	}
2366
2367	fn worst_case_slot() -> crate::access_list::Slot {
2368		let key = Key::try_from_var(vec![0xFFu8; limits::STORAGE_KEY_BYTES as usize])
2369			.expect("key fits STORAGE_KEY_BYTES bound; qed");
2370		crate::access_list::Slot::from(&key)
2371	}
2372
2373	fn near_full_access_list() -> crate::access_list::AccessList {
2374		let mut al = AccessList::new();
2375		for i in 0..(MAX_ACCESS_LIST_ENTRIES - 1) {
2376			al.touch(
2377				AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(i as u64) },
2378				StorageOp::Read,
2379			);
2380		}
2381		al
2382	}
2383
2384	#[benchmark(pov_mode = Ignored)]
2385	fn access_list_touch_cold_full() -> Result<(), BenchmarkError> {
2386		let mut al = near_full_access_list();
2387		// Insert a new entry (u64::MAX is past the fill range, so the touch is cold).
2388		let entry =
2389			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2390		let outcome;
2391		#[block]
2392		{
2393			outcome = al.touch(entry, StorageOp::Read);
2394		}
2395		assert!(outcome.is_cold());
2396		Ok(())
2397	}
2398
2399	#[benchmark(pov_mode = Ignored)]
2400	fn access_list_touch_hot_full() -> Result<(), BenchmarkError> {
2401		let mut al = near_full_access_list();
2402		// Worst-case hot touch: the rightmost key and the write upgrades the read-paid entry.
2403		let entry = AccessEntry {
2404			slot: worst_case_slot(),
2405			address: H160::from_low_u64_be(MAX_ACCESS_LIST_ENTRIES as u64 - 2),
2406		};
2407
2408		let touched = entry.clone();
2409		let outcome;
2410		#[block]
2411		{
2412			outcome = al.touch(touched, StorageOp::Write);
2413		}
2414		assert_eq!(
2415			outcome,
2416			Warmth::Hot { charged: StorageOp::Read },
2417			"the fill seeded this entry read-paid"
2418		);
2419		assert_eq!(
2420			al.peek(&entry),
2421			Warmth::Hot { charged: StorageOp::Write },
2422			"the write upgraded the entry"
2423		);
2424		Ok(())
2425	}
2426
2427	#[benchmark(pov_mode = Ignored)]
2428	fn access_list_touch_cold_empty() -> Result<(), BenchmarkError> {
2429		let mut al = AccessList::new();
2430		let entry =
2431			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2432		let outcome;
2433		#[block]
2434		{
2435			outcome = al.touch(entry, StorageOp::Read);
2436		}
2437		assert!(outcome.is_cold());
2438		Ok(())
2439	}
2440
2441	#[benchmark(pov_mode = Ignored)]
2442	fn access_list_touch_hot_single_element() -> Result<(), BenchmarkError> {
2443		let mut al = AccessList::new();
2444		let entry =
2445			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2446		al.touch(entry.clone(), StorageOp::Read);
2447		let outcome;
2448		#[block]
2449		{
2450			outcome = al.touch(entry, StorageOp::Read);
2451		}
2452		assert!(!outcome.is_cold());
2453		Ok(())
2454	}
2455
2456	// Per-entry rollback cost, prepaid by every cold touch since a frame revert
2457	// can't charge gas itself. Isolated by reverting a frame with exactly one
2458	// journaled entry on top of a near-full `AccessList`.
2459	#[benchmark(pov_mode = Ignored)]
2460	fn access_list_rollback_amortization() -> Result<(), BenchmarkError> {
2461		let mut al = near_full_access_list();
2462		al.enter_frame();
2463		al.touch(
2464			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) },
2465			StorageOp::Read,
2466		);
2467		#[block]
2468		{
2469			al.rollback_frame();
2470		}
2471		Ok(())
2472	}
2473
2474	// We use both full and empty benchmarks here instead of benchmarking transient_storage
2475	// (BTreeMap) directly. This approach is necessary because benchmarking this BTreeMap is very
2476	// slow. Additionally, we use linear regression for our benchmarks, and the BTreeMap's log(n)
2477	// complexity can introduce approximation errors.
2478	#[benchmark(pov_mode = Ignored)]
2479	fn set_transient_storage_empty() -> Result<(), BenchmarkError> {
2480		let max_value_len = limits::STORAGE_BYTES;
2481		let max_key_len = limits::STORAGE_KEY_BYTES;
2482		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2483			.map_err(|_| "Key has wrong length")?;
2484		let value = Some(vec![42u8; max_value_len as _]);
2485		let mut setup = CallSetup::<T>::default();
2486		let (mut ext, _) = setup.ext();
2487		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2488		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2489		let result;
2490		#[block]
2491		{
2492			result = runtime.ext().set_transient_storage(&key, value, false);
2493		}
2494
2495		assert_eq!(result, Ok(WriteOutcome::New));
2496		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2497		Ok(())
2498	}
2499
2500	#[benchmark(pov_mode = Ignored)]
2501	fn set_transient_storage_full() -> Result<(), BenchmarkError> {
2502		let max_value_len = limits::STORAGE_BYTES;
2503		let max_key_len = limits::STORAGE_KEY_BYTES;
2504		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2505			.map_err(|_| "Key has wrong length")?;
2506		let value = Some(vec![42u8; max_value_len as _]);
2507		let mut setup = CallSetup::<T>::default();
2508		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2509		let (mut ext, _) = setup.ext();
2510		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2511		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2512		let result;
2513		#[block]
2514		{
2515			result = runtime.ext().set_transient_storage(&key, value, false);
2516		}
2517
2518		assert_eq!(result, Ok(WriteOutcome::New));
2519		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2520		Ok(())
2521	}
2522
2523	#[benchmark(pov_mode = Ignored)]
2524	fn get_transient_storage_empty() -> Result<(), BenchmarkError> {
2525		let max_value_len = limits::STORAGE_BYTES;
2526		let max_key_len = limits::STORAGE_KEY_BYTES;
2527		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2528			.map_err(|_| "Key has wrong length")?;
2529
2530		let mut setup = CallSetup::<T>::default();
2531		let (mut ext, _) = setup.ext();
2532		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2533		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2534		runtime
2535			.ext()
2536			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2537			.map_err(|_| "Failed to write to transient storage during setup.")?;
2538		let result;
2539		#[block]
2540		{
2541			result = runtime.ext().get_transient_storage(&key);
2542		}
2543
2544		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2545		Ok(())
2546	}
2547
2548	#[benchmark(pov_mode = Ignored)]
2549	fn get_transient_storage_full() -> Result<(), BenchmarkError> {
2550		let max_value_len = limits::STORAGE_BYTES;
2551		let max_key_len = limits::STORAGE_KEY_BYTES;
2552		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2553			.map_err(|_| "Key has wrong length")?;
2554
2555		let mut setup = CallSetup::<T>::default();
2556		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2557		let (mut ext, _) = setup.ext();
2558		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2559		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2560		runtime
2561			.ext()
2562			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2563			.map_err(|_| "Failed to write to transient storage during setup.")?;
2564		let result;
2565		#[block]
2566		{
2567			result = runtime.ext().get_transient_storage(&key);
2568		}
2569
2570		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2571		Ok(())
2572	}
2573
2574	// The weight of journal rollbacks should be taken into account when setting storage.
2575	#[benchmark(pov_mode = Ignored)]
2576	fn rollback_transient_storage() -> Result<(), BenchmarkError> {
2577		let max_value_len = limits::STORAGE_BYTES;
2578		let max_key_len = limits::STORAGE_KEY_BYTES;
2579		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2580			.map_err(|_| "Key has wrong length")?;
2581
2582		let mut setup = CallSetup::<T>::default();
2583		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2584		let (mut ext, _) = setup.ext();
2585		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2586		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2587		runtime.ext().transient_storage().start_transaction();
2588		runtime
2589			.ext()
2590			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2591			.map_err(|_| "Failed to write to transient storage during setup.")?;
2592		#[block]
2593		{
2594			runtime.ext().transient_storage().rollback_transaction();
2595		}
2596
2597		assert_eq!(runtime.ext().get_transient_storage(&key), None);
2598		Ok(())
2599	}
2600
2601	// n: new byte size
2602	// o: old byte size
2603	#[benchmark(pov_mode = Measured)]
2604	fn seal_set_transient_storage(
2605		n: Linear<0, { limits::STORAGE_BYTES }>,
2606		o: Linear<0, { limits::STORAGE_BYTES }>,
2607	) -> Result<(), BenchmarkError> {
2608		let max_key_len = limits::STORAGE_KEY_BYTES;
2609		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2610			.map_err(|_| "Key has wrong length")?;
2611		let value = vec![1u8; n as usize];
2612		build_runtime!(runtime, memory: [ key.unhashed(), value.clone(), ]);
2613		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2614		runtime
2615			.ext()
2616			.set_transient_storage(&key, Some(vec![42u8; o as usize]), false)
2617			.map_err(|_| "Failed to write to transient storage during setup.")?;
2618
2619		let result;
2620		#[block]
2621		{
2622			result = runtime.bench_set_storage(
2623				memory.as_mut_slice(),
2624				StorageFlags::TRANSIENT.bits(),
2625				0,           // key_ptr
2626				max_key_len, // key_len
2627				max_key_len, // value_ptr
2628				n,           // value_len
2629			);
2630		}
2631
2632		assert_ok!(result);
2633		assert_eq!(runtime.ext().get_transient_storage(&key).unwrap(), value);
2634		Ok(())
2635	}
2636
2637	#[benchmark(pov_mode = Measured)]
2638	fn seal_clear_transient_storage(
2639		n: Linear<0, { limits::STORAGE_BYTES }>,
2640	) -> Result<(), BenchmarkError> {
2641		let max_key_len = limits::STORAGE_KEY_BYTES;
2642		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2643			.map_err(|_| "Key has wrong length")?;
2644		let input_bytes = IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
2645			flags: StorageFlags::TRANSIENT.bits(),
2646			key: vec![0u8; max_key_len as usize].into(),
2647			isFixedKey: false,
2648		})
2649		.abi_encode();
2650
2651		let mut call_setup = CallSetup::<T>::default();
2652		let (mut ext, _) = call_setup.ext();
2653		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2654			.map_err(|_| "Failed to write to transient storage during setup.")?;
2655
2656		let result;
2657		#[block]
2658		{
2659			result = run_builtin_precompile(
2660				&mut ext,
2661				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2662				input_bytes,
2663			);
2664		}
2665		assert_ok!(result);
2666		assert!(ext.get_transient_storage(&key).is_none());
2667
2668		Ok(())
2669	}
2670
2671	#[benchmark(pov_mode = Measured)]
2672	fn seal_get_transient_storage(
2673		n: Linear<0, { limits::STORAGE_BYTES }>,
2674	) -> Result<(), BenchmarkError> {
2675		let max_key_len = limits::STORAGE_KEY_BYTES;
2676		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2677			.map_err(|_| "Key has wrong length")?;
2678		build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2679		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2680		runtime
2681			.ext()
2682			.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2683			.map_err(|_| "Failed to write to transient storage during setup.")?;
2684
2685		let out_ptr = max_key_len + 4;
2686		let result;
2687		#[block]
2688		{
2689			result = runtime.bench_get_storage(
2690				memory.as_mut_slice(),
2691				StorageFlags::TRANSIENT.bits(),
2692				0,           // key_ptr
2693				max_key_len, // key_len
2694				out_ptr,     // out_ptr
2695				max_key_len, // out_len_ptr
2696			);
2697		}
2698
2699		assert_ok!(result);
2700		assert_eq!(
2701			&runtime.ext().get_transient_storage(&key).unwrap(),
2702			&memory[out_ptr as usize..]
2703		);
2704		Ok(())
2705	}
2706
2707	#[benchmark(pov_mode = Measured)]
2708	fn seal_contains_transient_storage(
2709		n: Linear<0, { limits::STORAGE_BYTES }>,
2710	) -> Result<(), BenchmarkError> {
2711		let max_key_len = limits::STORAGE_KEY_BYTES;
2712		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2713			.map_err(|_| "Key has wrong length")?;
2714
2715		let input_bytes = IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
2716			flags: StorageFlags::TRANSIENT.bits(),
2717			key: vec![0u8; max_key_len as usize].into(),
2718			isFixedKey: false,
2719		})
2720		.abi_encode();
2721
2722		let mut call_setup = CallSetup::<T>::default();
2723		let (mut ext, _) = call_setup.ext();
2724		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2725			.map_err(|_| "Failed to write to transient storage during setup.")?;
2726
2727		let result;
2728		#[block]
2729		{
2730			result = run_builtin_precompile(
2731				&mut ext,
2732				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2733				input_bytes,
2734			);
2735		}
2736		assert!(result.is_ok());
2737		assert!(ext.get_transient_storage(&key).is_some());
2738
2739		Ok(())
2740	}
2741
2742	#[benchmark(pov_mode = Measured)]
2743	fn seal_take_transient_storage(
2744		n: Linear<0, { limits::STORAGE_BYTES }>,
2745	) -> Result<(), BenchmarkError> {
2746		let n = limits::STORAGE_BYTES;
2747		let value = vec![42u8; n as usize];
2748		let max_key_len = limits::STORAGE_KEY_BYTES;
2749		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2750			.map_err(|_| "Key has wrong length")?;
2751
2752		let input_bytes = IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
2753			flags: StorageFlags::TRANSIENT.bits(),
2754			key: vec![0u8; max_key_len as usize].into(),
2755			isFixedKey: false,
2756		})
2757		.abi_encode();
2758
2759		let mut call_setup = CallSetup::<T>::default();
2760		let (mut ext, _) = call_setup.ext();
2761		ext.set_transient_storage(&key, Some(value), false)
2762			.map_err(|_| "Failed to write to transient storage during setup.")?;
2763
2764		let result;
2765		#[block]
2766		{
2767			result = run_builtin_precompile(
2768				&mut ext,
2769				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2770				input_bytes,
2771			);
2772		}
2773		assert!(result.is_ok());
2774		assert!(ext.get_transient_storage(&key).is_none());
2775
2776		Ok(())
2777	}
2778
2779	// t: with or without some value to transfer
2780	// d: with or without dust value to transfer
2781	// i: size of the input data
2782	#[benchmark(pov_mode = Measured)]
2783	fn seal_call(
2784		t: Linear<0, 1>,
2785		d: Linear<0, 1>,
2786		i: Linear<0, { limits::code::BLOB_BYTES }>,
2787	) -> Result<(), BenchmarkError> {
2788		// An EIP-7702 delegated callee is the worst case for the account resolution in
2789		// `new_frame`: the `AccountInfoOf` entry decodes the larger `DelegatedEOA` variant and
2790		// the call still runs the target's code. (A callee whose delegation snapshot is empty
2791		// costs a second read of the target but skips code load and execution entirely, so it
2792		// is cheaper overall.)
2793		let target = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?;
2794		let callee_addr = H160([0x42; 20]);
2795		let callee = delegated_eoa::<T>(callee_addr, target.address)?;
2796		// Keep the origin's budget the same as with a contract callee. A contract already exists
2797		// in `System`, so `Stack::transfer` skips the "create the destination" arm; a fresh EOA
2798		// does not, and that arm charges the destination's ED to the origin, leaving it nothing
2799		// for `ensure_sufficient_dust` to burn into dust when `d == 1`.
2800		T::Currency::set_balance(&callee, Pallet::<T>::min_balance());
2801
2802		let callee_bytes = callee.encode();
2803		let callee_len = callee_bytes.len() as u32;
2804
2805		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2806		let dust = 100u32 * d;
2807		let evm_value =
2808			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2809		let value_bytes = evm_value.encode();
2810
2811		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2812		let deposit_bytes = Into::<U256>::into(deposit).encode();
2813		let deposit_len = deposit_bytes.len() as u32;
2814
2815		let mut setup = CallSetup::<T>::default();
2816		setup.set_storage_deposit_limit(deposit);
2817		// We benchmark the overhead of cloning the input. Not passing it to the contract.
2818		// This is why we set the input here instead of passig it as pointer to the `bench_call`.
2819		setup.set_data(vec![42; i as usize]);
2820		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2821		setup.set_balance(value + 1u32.into() + Pallet::<T>::min_balance());
2822
2823		let (mut ext, _) = setup.ext();
2824		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2825		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,);
2826		let before = Pallet::<T>::evm_balance(&callee_addr);
2827
2828		let result;
2829		#[block]
2830		{
2831			result = runtime.bench_call(
2832				memory.as_mut_slice(),
2833				pack_hi_lo(CallFlags::CLONE_INPUT.bits(), 0), // flags + callee
2834				u64::MAX,                                     // ref_time_limit
2835				u64::MAX,                                     // proof_size_limit
2836				pack_hi_lo(callee_len, callee_len + deposit_len), // deposit_ptr + value_pr
2837				pack_hi_lo(0, 0),                             // input len + data ptr
2838				pack_hi_lo(0, SENTINEL),                      // output len + data ptr
2839			);
2840		}
2841
2842		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2843		assert_eq!(
2844			Pallet::<T>::evm_balance(&callee_addr),
2845			before + evm_value,
2846			"{callee_addr:?} balance should have grown by {evm_value:?}"
2847		);
2848
2849		Ok(())
2850	}
2851
2852	// d: 1 if the associated pre-compile has a contract info that needs to be loaded
2853	// i: size of the input data
2854	#[benchmark(pov_mode = Measured)]
2855	fn seal_call_precompile(d: Linear<0, 1>, i: Linear<0, { limits::CALLDATA_BYTES - 100 }>) {
2856		use alloy_core::sol_types::SolInterface;
2857		use precompiles::{BenchmarkNoInfo, BenchmarkWithInfo, BuiltinPrecompile, IBenchmarking};
2858
2859		let callee_bytes = if d == 1 {
2860			BenchmarkWithInfo::<T>::MATCHER.base_address().to_vec()
2861		} else {
2862			BenchmarkNoInfo::<T>::MATCHER.base_address().to_vec()
2863		};
2864		let callee_len = callee_bytes.len() as u32;
2865
2866		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2867		let deposit_bytes = Into::<U256>::into(deposit).encode();
2868		let deposit_len = deposit_bytes.len() as u32;
2869
2870		let value: BalanceOf<T> = Zero::zero();
2871		let value_bytes = Into::<U256>::into(value).encode();
2872		let value_len = value_bytes.len() as u32;
2873
2874		let input_bytes = IBenchmarking::IBenchmarkingCalls::bench(IBenchmarking::benchCall {
2875			input: vec![42_u8; i as usize].into(),
2876		})
2877		.abi_encode();
2878		let input_len = input_bytes.len() as u32;
2879
2880		let mut setup = CallSetup::<T>::default();
2881		setup.set_storage_deposit_limit(deposit);
2882
2883		let (mut ext, _) = setup.ext();
2884		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2885		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes, input_bytes,);
2886
2887		let mut do_benchmark = || {
2888			runtime.bench_call(
2889				memory.as_mut_slice(),
2890				pack_hi_lo(0, 0), // flags + callee
2891				u64::MAX,         // ref_time_limit
2892				u64::MAX,         // proof_size_limit
2893				pack_hi_lo(callee_len, callee_len + deposit_len), /* deposit_ptr +
2894				                   * value_pr */
2895				pack_hi_lo(input_len, callee_len + deposit_len + value_len), /* input len +
2896				                                                              * input ptr */
2897				pack_hi_lo(0, SENTINEL), // output len + output ptr
2898			)
2899		};
2900
2901		// first call of the pre-compile will create its contract info and account
2902		// so we make sure to create it
2903		assert_eq!(do_benchmark().unwrap(), ReturnErrorCode::Success);
2904
2905		let result;
2906		#[block]
2907		{
2908			result = do_benchmark();
2909		}
2910
2911		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2912	}
2913
2914	#[benchmark(pov_mode = Measured)]
2915	fn seal_delegate_call() -> Result<(), BenchmarkError> {
2916		// Delegated code source: worst case for the callee resolution in `new_frame`.
2917		// See `seal_call`.
2918		let target = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?;
2919		let address = delegated_eoa::<T>(H160([0x43; 20]), target.address)?;
2920
2921		let address_bytes = address.encode();
2922		let address_len = address_bytes.len() as u32;
2923
2924		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2925		let deposit_bytes = Into::<U256>::into(deposit).encode();
2926
2927		let mut setup = CallSetup::<T>::default();
2928		setup.set_storage_deposit_limit(deposit);
2929		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2930
2931		let (mut ext, _) = setup.ext();
2932		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2933		let mut memory = memory!(address_bytes, deposit_bytes,);
2934
2935		let result;
2936		#[block]
2937		{
2938			result = runtime.bench_delegate_call(
2939				memory.as_mut_slice(),
2940				pack_hi_lo(0, 0),        // flags + address ptr
2941				u64::MAX,                // ref_time_limit
2942				u64::MAX,                // proof_size_limit
2943				address_len,             // deposit_ptr
2944				pack_hi_lo(0, 0),        // input len + data ptr
2945				pack_hi_lo(0, SENTINEL), // output len + ptr
2946			);
2947		}
2948
2949		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2950		Ok(())
2951	}
2952
2953	// t: with or without some value to transfer
2954	// d: with or without dust value to transfer
2955	// i: size of the input data
2956	#[benchmark(pov_mode = Measured)]
2957	fn seal_instantiate(
2958		t: Linear<0, 1>,
2959		d: Linear<0, 1>,
2960		i: Linear<0, { limits::CALLDATA_BYTES }>,
2961	) -> Result<(), BenchmarkError> {
2962		let code = VmBinaryModule::dummy();
2963		let hash = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?.info()?.code_hash;
2964		let hash_bytes = hash.encode();
2965
2966		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2967		let dust = 100u32 * d;
2968		let evm_value =
2969			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2970		let value_bytes = evm_value.encode();
2971		let value_len = value_bytes.len() as u32;
2972
2973		let deposit: BalanceOf<T> = BalanceOf::<T>::max_value();
2974		let deposit_bytes = Into::<U256>::into(deposit).encode();
2975		let deposit_len = deposit_bytes.len() as u32;
2976
2977		let mut setup = CallSetup::<T>::default();
2978		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2979		setup.set_balance(value + 1u32.into() + (Pallet::<T>::min_balance() * 2u32.into()));
2980
2981		let account_id = &setup.contract().account_id.clone();
2982		let (mut ext, _) = setup.ext();
2983		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2984
2985		let input = vec![42u8; i as _];
2986		let input_len = hash_bytes.len() as u32 + input.len() as u32;
2987		let salt = [42u8; 32];
2988		let deployer = T::AddressMapper::to_address(&account_id);
2989		let addr = crate::address::create2(&deployer, &code.code, &input, &salt);
2990		let mut memory = memory!(hash_bytes, input, deposit_bytes, value_bytes, salt,);
2991
2992		let mut offset = {
2993			let mut current = 0u32;
2994			move |after: u32| {
2995				current += after;
2996				current
2997			}
2998		};
2999
3000		assert!(AccountInfoOf::<T>::get(&addr).is_none());
3001
3002		let result;
3003		#[block]
3004		{
3005			result = runtime.bench_instantiate(
3006				memory.as_mut_slice(),
3007				u64::MAX,                                           // ref_time_limit
3008				u64::MAX,                                           // proof_size_limit
3009				pack_hi_lo(offset(input_len), offset(deposit_len)), // deposit_ptr + value_ptr
3010				pack_hi_lo(input_len, 0),                           // input_data_len + input_data
3011				pack_hi_lo(0, SENTINEL),                            // output_len_ptr + output_ptr
3012				pack_hi_lo(SENTINEL, offset(value_len)),            // address_ptr + salt_ptr
3013			);
3014		}
3015
3016		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
3017		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
3018
3019		assert_eq!(
3020			Pallet::<T>::evm_balance(&addr),
3021			evm_value,
3022			"{addr:?} balance should hold {evm_value:?}"
3023		);
3024		Ok(())
3025	}
3026
3027	// t: with or without some value to transfer
3028	// d: with or without dust value to transfer
3029	// i: size of the init code (max 49152 bytes per EIP-3860)
3030	#[benchmark(pov_mode = Measured)]
3031	fn evm_instantiate(
3032		t: Linear<0, 1>,
3033		d: Linear<0, 1>,
3034		i: Linear<{ 10 * 1024 }, { 48 * 1024 }>,
3035	) -> Result<(), BenchmarkError> {
3036		use crate::vm::evm::instructions::BENCH_INIT_CODE;
3037		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_init_code_for_runtime_size(0));
3038		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
3039		setup.set_balance(caller_funding::<T>());
3040
3041		let (mut ext, _) = setup.ext();
3042		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
3043
3044		let value = {
3045			let value: BalanceOf<T> = (1_000_000u32 * t).into();
3046			let dust = 100u32 * d;
3047			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust))
3048		};
3049
3050		let init_code = vec![BENCH_INIT_CODE; i as usize];
3051		let _ = interpreter.memory.resize(0, init_code.len());
3052		let salt = U256::from(42u64);
3053		interpreter.memory.set_data(0, 0, init_code.len(), &init_code);
3054
3055		// Setup stack for create instruction [value, offset, size, salt]
3056		let _ = interpreter.stack.push(salt);
3057		let _ = interpreter.stack.push(U256::from(init_code.len()));
3058		let _ = interpreter.stack.push(U256::zero());
3059		let _ = interpreter.stack.push(value);
3060
3061		let result;
3062		#[block]
3063		{
3064			result = instructions::contract::create::<true, _>(&mut interpreter);
3065		}
3066
3067		assert!(result.is_continue());
3068		let addr = interpreter.stack.top().unwrap().into_address();
3069		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
3070		assert_eq!(Pallet::<T>::code(&addr).len(), revm::primitives::eip170::MAX_CODE_SIZE);
3071		assert_eq!(Pallet::<T>::evm_balance(&addr), value, "balance should hold {value:?}");
3072		Ok(())
3073	}
3074
3075	// `n`: Input to hash in bytes
3076	#[benchmark(pov_mode = Measured)]
3077	fn sha2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3078		let input = vec![0u8; n as usize];
3079		let mut call_setup = CallSetup::<T>::default();
3080		let (mut ext, _) = call_setup.ext();
3081
3082		let result;
3083		#[block]
3084		{
3085			result = run_builtin_precompile(
3086				&mut ext,
3087				H160::from_low_u64_be(2).as_fixed_bytes(),
3088				input.clone(),
3089			);
3090		}
3091		assert_eq!(sp_io::hashing::sha2_256(&input).to_vec(), result.unwrap().data);
3092	}
3093
3094	#[benchmark(pov_mode = Measured)]
3095	fn identity(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3096		let input = vec![0u8; n as usize];
3097		let mut call_setup = CallSetup::<T>::default();
3098		let (mut ext, _) = call_setup.ext();
3099
3100		let result;
3101		#[block]
3102		{
3103			result = run_builtin_precompile(
3104				&mut ext,
3105				H160::from_low_u64_be(4).as_fixed_bytes(),
3106				input.clone(),
3107			);
3108		}
3109		assert_eq!(input, result.unwrap().data);
3110	}
3111
3112	// `n`: Input to hash in bytes
3113	#[benchmark(pov_mode = Measured)]
3114	fn ripemd_160(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3115		use ripemd::Digest;
3116		let input = vec![0u8; n as usize];
3117		let mut call_setup = CallSetup::<T>::default();
3118		let (mut ext, _) = call_setup.ext();
3119
3120		let result;
3121		#[block]
3122		{
3123			result = run_builtin_precompile(
3124				&mut ext,
3125				H160::from_low_u64_be(3).as_fixed_bytes(),
3126				input.clone(),
3127			);
3128		}
3129		let mut expected = [0u8; 32];
3130		expected[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input));
3131
3132		assert_eq!(expected.to_vec(), result.unwrap().data);
3133	}
3134
3135	// `n`: Input to hash in bytes
3136	#[benchmark(pov_mode = Measured)]
3137	fn seal_hash_keccak_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3138		build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]);
3139
3140		let result;
3141		#[block]
3142		{
3143			result = runtime.bench_hash_keccak_256(memory.as_mut_slice(), 32, n, 0);
3144		}
3145		assert_eq!(sp_io::hashing::keccak_256(&memory[32..]), &memory[0..32]);
3146		assert_ok!(result);
3147	}
3148
3149	// `n`: Input to hash in bytes
3150	#[benchmark(pov_mode = Measured)]
3151	fn hash_blake2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3152		let input = vec![0u8; n as usize];
3153		let input_bytes = ISystem::ISystemCalls::hashBlake256(ISystem::hashBlake256Call {
3154			input: input.clone().into(),
3155		})
3156		.abi_encode();
3157
3158		let mut call_setup = CallSetup::<T>::default();
3159		let (mut ext, _) = call_setup.ext();
3160
3161		let result;
3162		#[block]
3163		{
3164			result = run_builtin_precompile(
3165				&mut ext,
3166				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
3167				input_bytes,
3168			);
3169		}
3170		let truth: [u8; 32] = sp_io::hashing::blake2_256(&input);
3171		let truth = FixedBytes::<32>::abi_encode(&truth);
3172		let truth = FixedBytes::<32>::abi_decode(&truth[..]).expect("decoding failed");
3173
3174		let raw_data = result.unwrap().data;
3175		let ret_hash = FixedBytes::<32>::abi_decode(&raw_data[..]).expect("decoding failed");
3176		assert_eq!(truth, ret_hash);
3177	}
3178
3179	// `n`: Input to hash in bytes
3180	#[benchmark(pov_mode = Measured)]
3181	fn hash_blake2_128(n: Linear<0, { limits::code::BLOB_BYTES }>) {
3182		let input = vec![0u8; n as usize];
3183		let input_bytes = ISystem::ISystemCalls::hashBlake128(ISystem::hashBlake128Call {
3184			input: input.clone().into(),
3185		})
3186		.abi_encode();
3187
3188		let mut call_setup = CallSetup::<T>::default();
3189		let (mut ext, _) = call_setup.ext();
3190
3191		let result;
3192		#[block]
3193		{
3194			result = run_builtin_precompile(
3195				&mut ext,
3196				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
3197				input_bytes,
3198			);
3199		}
3200		let truth: [u8; 16] = sp_io::hashing::blake2_128(&input);
3201		let truth = FixedBytes::<16>::abi_encode(&truth);
3202		let truth = FixedBytes::<16>::abi_decode(&truth[..]).expect("decoding failed");
3203
3204		let raw_data = result.unwrap().data;
3205		let ret_hash = FixedBytes::<16>::abi_decode(&raw_data[..]).expect("decoding failed");
3206		assert_eq!(truth, ret_hash);
3207	}
3208
3209	// `n`: Message input length to verify in bytes.
3210	// need some buffer so the code size does not exceed the max code size.
3211	#[benchmark(pov_mode = Measured)]
3212	fn seal_sr25519_verify(n: Linear<0, { limits::code::BLOB_BYTES - 255 }>) {
3213		let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::<Vec<_>>();
3214		let message_len = message.len() as u32;
3215
3216		let key_type = sp_core::crypto::KeyTypeId(*b"code");
3217		let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
3218		let sig =
3219			sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
3220		let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec();
3221		let sig_len = sig.len() as u32;
3222
3223		build_runtime!(runtime, memory: [sig, pub_key.to_vec(), message, ]);
3224
3225		let result;
3226		#[block]
3227		{
3228			result = runtime.bench_sr25519_verify(
3229				memory.as_mut_slice(),
3230				0,                              // signature_ptr
3231				sig_len,                        // pub_key_ptr
3232				message_len,                    // message_len
3233				sig_len + pub_key.len() as u32, // message_ptr
3234			);
3235		}
3236
3237		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
3238	}
3239
3240	#[benchmark(pov_mode = Measured)]
3241	fn ecdsa_recover() {
3242		use hex_literal::hex;
3243		let input = hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549").to_vec();
3244		let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b");
3245		let mut call_setup = CallSetup::<T>::default();
3246		let (mut ext, _) = call_setup.ext();
3247
3248		let result;
3249
3250		#[block]
3251		{
3252			result =
3253				run_builtin_precompile(&mut ext, H160::from_low_u64_be(1).as_fixed_bytes(), input);
3254		}
3255
3256		assert_eq!(result.unwrap().data, expected);
3257	}
3258
3259	#[benchmark(pov_mode = Measured)]
3260	fn p256_verify() {
3261		use hex_literal::hex;
3262		let input = hex!("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").to_vec();
3263		let expected = U256::one().to_big_endian();
3264		let mut call_setup = CallSetup::<T>::default();
3265		let (mut ext, _) = call_setup.ext();
3266
3267		let result;
3268
3269		#[block]
3270		{
3271			result = run_builtin_precompile(
3272				&mut ext,
3273				H160::from_low_u64_be(0x100).as_fixed_bytes(),
3274				input,
3275			);
3276		}
3277
3278		assert_eq!(result.unwrap().data, expected);
3279	}
3280
3281	#[benchmark(pov_mode = Measured)]
3282	fn bn128_add() {
3283		use hex_literal::hex;
3284		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b3625f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd5850b38c7ced6e4daef9c4347f370d6d8b58f4b1d8dc61a3c59d651a0644a2a27cf").to_vec();
3285		let expected = hex!(
3286			"0a6678fd675aa4d8f0d03a1feb921a27f38ebdcb860cc083653519655acd6d79172fd5b3b2bfdd44e43bcec3eace9347608f9f0a16f1e184cb3f52e6f259cbeb"
3287		);
3288		let mut call_setup = CallSetup::<T>::default();
3289		let (mut ext, _) = call_setup.ext();
3290
3291		let result;
3292		#[block]
3293		{
3294			result =
3295				run_builtin_precompile(&mut ext, H160::from_low_u64_be(6).as_fixed_bytes(), input);
3296		}
3297
3298		assert_eq!(result.unwrap().data, expected);
3299	}
3300
3301	#[benchmark(pov_mode = Measured)]
3302	fn bn128_mul() {
3303		use hex_literal::hex;
3304		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").to_vec();
3305		let expected = hex!(
3306			"0bf982b98a2757878c051bfe7eee228b12bc69274b918f08d9fcb21e9184ddc10b17c77cbf3c19d5d27e18cbd4a8c336afb488d0e92c18d56e64dd4ea5c437e6"
3307		);
3308		let mut call_setup = CallSetup::<T>::default();
3309		let (mut ext, _) = call_setup.ext();
3310
3311		let result;
3312		#[block]
3313		{
3314			result =
3315				run_builtin_precompile(&mut ext, H160::from_low_u64_be(7).as_fixed_bytes(), input);
3316		}
3317
3318		assert_eq!(result.unwrap().data, expected);
3319	}
3320
3321	// `n`: pairings to perform
3322	#[benchmark(pov_mode = Measured)]
3323	fn bn128_pairing(n: Linear<0, { 20 }>) {
3324		fn generate_random_ecpairs(n: usize) -> Vec<u8> {
3325			use bn::{AffineG1, AffineG2, Fr, G1, G2, Group};
3326			use rand::SeedableRng;
3327			use rand_pcg::Pcg64;
3328			let mut rng = Pcg64::seed_from_u64(1);
3329
3330			let mut buffer = vec![0u8; n * 192];
3331
3332			let mut write = |element: &bn::Fq, offset: &mut usize| {
3333				element.to_big_endian(&mut buffer[*offset..*offset + 32]).unwrap();
3334				*offset += 32
3335			};
3336
3337			for i in 0..n {
3338				let mut offset = i * 192;
3339				let scalar = Fr::random(&mut rng);
3340
3341				let g1 = G1::one() * scalar;
3342				let g2 = G2::one() * scalar;
3343				let a = AffineG1::from_jacobian(g1).expect("G1 point should be on curve");
3344				let b = AffineG2::from_jacobian(g2).expect("G2 point should be on curve");
3345
3346				write(&a.x(), &mut offset);
3347				write(&a.y(), &mut offset);
3348				write(&b.x().imaginary(), &mut offset);
3349				write(&b.x().real(), &mut offset);
3350				write(&b.y().imaginary(), &mut offset);
3351				write(&b.y().real(), &mut offset);
3352			}
3353
3354			buffer
3355		}
3356
3357		let input = generate_random_ecpairs(n as usize);
3358		let mut call_setup = CallSetup::<T>::default();
3359		let (mut ext, _) = call_setup.ext();
3360
3361		let result;
3362		#[block]
3363		{
3364			result =
3365				run_builtin_precompile(&mut ext, H160::from_low_u64_be(8).as_fixed_bytes(), input);
3366		}
3367		assert_ok!(result);
3368	}
3369
3370	// `n`: number of rounds to perform
3371	#[benchmark(pov_mode = Measured)]
3372	fn blake2f(n: Linear<0, 1200>) {
3373		use hex_literal::hex;
3374		let input = hex!(
3375			"48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"
3376		);
3377		let input = n.to_be_bytes().to_vec().into_iter().chain(input.to_vec()).collect::<Vec<_>>();
3378		let mut call_setup = CallSetup::<T>::default();
3379		let (mut ext, _) = call_setup.ext();
3380
3381		let result;
3382		#[block]
3383		{
3384			result =
3385				run_builtin_precompile(&mut ext, H160::from_low_u64_be(9).as_fixed_bytes(), input);
3386		}
3387		assert_ok!(result);
3388	}
3389
3390	// Only calling the function itself for the list of
3391	// generated different ECDSA keys.
3392	// This is a slow call: We reduce the number of runs.
3393	#[benchmark(pov_mode = Measured)]
3394	fn seal_ecdsa_to_eth_address() {
3395		let key_type = sp_core::crypto::KeyTypeId(*b"code");
3396		let pub_key_bytes = sp_io::crypto::ecdsa_generate(key_type, None).0;
3397		build_runtime!(runtime, memory: [[0u8; 20], pub_key_bytes,]);
3398
3399		let result;
3400		#[block]
3401		{
3402			result = runtime.bench_ecdsa_to_eth_address(
3403				memory.as_mut_slice(),
3404				20, // key_ptr
3405				0,  // output_ptr
3406			);
3407		}
3408
3409		assert_ok!(result);
3410		assert_eq!(&memory[..20], runtime.ext().ecdsa_to_eth_address(&pub_key_bytes).unwrap());
3411	}
3412
3413	/// Benchmark the cost of executing `r` noop (JUMPDEST) instructions.
3414	#[benchmark(pov_mode = Measured)]
3415	fn evm_opcode(r: Linear<0, 10_000>) -> Result<(), BenchmarkError> {
3416		let module = VmBinaryModule::evm_noop(r);
3417		let inputs = vec![];
3418
3419		let code = Bytecode::new_raw(revm::primitives::Bytes::from(module.code.clone()));
3420		let mut setup = CallSetup::<T>::new(module);
3421		let (mut ext, _) = setup.ext();
3422
3423		let result;
3424		#[block]
3425		{
3426			result = evm::call(code, &mut ext, inputs);
3427		}
3428
3429		assert!(result.is_ok());
3430		Ok(())
3431	}
3432
3433	/// Benchmarks `r` EVM `POP` op-codes.
3434	#[benchmark(pov_mode = Measured)]
3435	fn evm_pop_opcode(r: Linear<0, EVM_STACK_LIMIT>) {
3436		let code = Bytecode::new_raw(vec![POP; r as usize].into());
3437		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3438		let (mut ext, _) = setup.ext();
3439		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3440		setup_stack(&mut interpreter, vec![U256::MAX; r as usize]);
3441
3442		evict_caches();
3443		let result;
3444		#[block]
3445		{
3446			result = evm::run_plain(&mut interpreter);
3447		}
3448
3449		let ControlFlow::Break(halt) = result;
3450		assert!(matches!(halt, Halt::Stop));
3451		assert_eq!(interpreter.stack.len(), 0);
3452	}
3453
3454	/// Benchmarks `r` EVM `DIV` op-codes.
3455	///
3456	/// # Added Overheads
3457	///
3458	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3459	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3460	///   the divisor is picked at random from [`knuth_division_operands`].
3461	#[benchmark(pov_mode = Measured)]
3462	fn evm_div_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3463		use knuth_division_operands::DENOMINATORS;
3464
3465		let mut rng = Pcg64::seed_from_u64(1337);
3466		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3467
3468		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3469		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3470		let (mut ext, _) = setup.ext();
3471		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3472		setup_stack(&mut interpreter, operands);
3473
3474		evict_caches();
3475		let result;
3476		#[block]
3477		{
3478			result = evm::run_plain(&mut interpreter);
3479		}
3480
3481		let ControlFlow::Break(halt) = result;
3482		assert!(matches!(halt, Halt::Stop));
3483		assert_eq!(interpreter.stack.len(), 0);
3484	}
3485
3486	/// Benchmarks `r` EVM `DIV` op-codes with pseudo-random one-limb correction patterns.
3487	#[benchmark(pov_mode = Measured)]
3488	fn evm_div_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3489		use one_limb_division_operands::DENOMINATORS;
3490
3491		let mut rng = Pcg64::seed_from_u64(1337);
3492		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3493
3494		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3495		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3496		let (mut ext, _) = setup.ext();
3497		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3498		setup_stack(&mut interpreter, operands);
3499
3500		evict_caches();
3501		let result;
3502		#[block]
3503		{
3504			result = evm::run_plain(&mut interpreter);
3505		}
3506
3507		let ControlFlow::Break(halt) = result;
3508		assert!(matches!(halt, Halt::Stop));
3509		assert_eq!(interpreter.stack.len(), 0);
3510	}
3511
3512	/// Benchmarks `r` EVM `DIV` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3513	#[benchmark(pov_mode = Measured)]
3514	fn evm_div_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3515		let mut rng = Pcg64::seed_from_u64(1337);
3516		let operands = (0..r).flat_map(|_| mixed_division_operands::unsigned_operands(&mut rng));
3517
3518		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3519		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3520		let (mut ext, _) = setup.ext();
3521		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3522		setup_stack(&mut interpreter, operands);
3523
3524		evict_caches();
3525		let result;
3526		#[block]
3527		{
3528			result = evm::run_plain(&mut interpreter);
3529		}
3530
3531		let ControlFlow::Break(halt) = result;
3532		assert!(matches!(halt, Halt::Stop));
3533		assert_eq!(interpreter.stack.len(), 0);
3534	}
3535
3536	/// Benchmarks `r` EVM `DIV` op-codes with the selected fixed Knuth divisor.
3537	#[benchmark(pov_mode = Measured)]
3538	fn evm_div_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3539		use knuth_division_operands::{DIV_FIXED_DIVISOR, NUMERATOR};
3540
3541		let operands = (0..r).flat_map(|_| [DIV_FIXED_DIVISOR, NUMERATOR]);
3542		let code = Bytecode::new_raw([DIV, POP].repeat(r as usize).into());
3543		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3544		let (mut ext, _) = setup.ext();
3545		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3546		setup_stack(&mut interpreter, operands);
3547
3548		evict_caches();
3549		let result;
3550		#[block]
3551		{
3552			result = evm::run_plain(&mut interpreter);
3553		}
3554
3555		let ControlFlow::Break(halt) = result;
3556		assert!(matches!(halt, Halt::Stop));
3557		assert_eq!(interpreter.stack.len(), 0);
3558	}
3559
3560	/// Benchmarks `r` EVM `SDIV` op-codes.
3561	///
3562	/// # Added Overheads
3563	///
3564	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3565	/// * A carry through every limb to negate the numerator, since it's the most negative value.
3566	/// * No early exit from the check against the most negative value, since the numerator is that
3567	///   value.
3568	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3569	///   the divisor is picked at random from [`knuth_division_operands`].
3570	/// * A branch misprediction on whether the divisor and the quotient are negated, since the
3571	///   divisor's sign is pseudo-random.
3572	#[benchmark(pov_mode = Measured)]
3573	fn evm_sdiv_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3574		use knuth_division_operands::DENOMINATORS;
3575
3576		let mut rng = Pcg64::seed_from_u64(1337);
3577		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3578
3579		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3580		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3581		let (mut ext, _) = setup.ext();
3582		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3583		setup_stack(&mut interpreter, operands);
3584
3585		evict_caches();
3586		let result;
3587		#[block]
3588		{
3589			result = evm::run_plain(&mut interpreter);
3590		}
3591
3592		let ControlFlow::Break(halt) = result;
3593		assert!(matches!(halt, Halt::Stop));
3594		assert_eq!(interpreter.stack.len(), 0);
3595	}
3596
3597	/// Benchmarks `r` EVM `SDIV` op-codes with pseudo-random one-limb correction patterns.
3598	#[benchmark(pov_mode = Measured)]
3599	fn evm_sdiv_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3600		use one_limb_division_operands::DENOMINATORS;
3601
3602		let mut rng = Pcg64::seed_from_u64(1337);
3603		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3604
3605		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3606		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3607		let (mut ext, _) = setup.ext();
3608		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3609		setup_stack(&mut interpreter, operands);
3610
3611		evict_caches();
3612		let result;
3613		#[block]
3614		{
3615			result = evm::run_plain(&mut interpreter);
3616		}
3617
3618		let ControlFlow::Break(halt) = result;
3619		assert!(matches!(halt, Halt::Stop));
3620		assert_eq!(interpreter.stack.len(), 0);
3621	}
3622
3623	/// Benchmarks `r` EVM `SDIV` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3624	#[benchmark(pov_mode = Measured)]
3625	fn evm_sdiv_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3626		let mut rng = Pcg64::seed_from_u64(1337);
3627		let operands = (0..r).flat_map(|_| mixed_division_operands::signed_operands(&mut rng));
3628
3629		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3630		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3631		let (mut ext, _) = setup.ext();
3632		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3633		setup_stack(&mut interpreter, operands);
3634
3635		evict_caches();
3636		let result;
3637		#[block]
3638		{
3639			result = evm::run_plain(&mut interpreter);
3640		}
3641
3642		let ControlFlow::Break(halt) = result;
3643		assert!(matches!(halt, Halt::Stop));
3644		assert_eq!(interpreter.stack.len(), 0);
3645	}
3646
3647	/// Benchmarks `r` EVM `SDIV` op-codes with the selected fixed Knuth divisor.
3648	#[benchmark(pov_mode = Measured)]
3649	fn evm_sdiv_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3650		use knuth_division_operands::{SDIV_FIXED_DIVISOR, fixed_signed_operands};
3651
3652		let mut rng = Pcg64::seed_from_u64(1337);
3653		let operands = (0..r).flat_map(|_| fixed_signed_operands(&mut rng, SDIV_FIXED_DIVISOR));
3654
3655		let code = Bytecode::new_raw([SDIV, POP].repeat(r as usize).into());
3656		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3657		let (mut ext, _) = setup.ext();
3658		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3659		setup_stack(&mut interpreter, operands);
3660
3661		evict_caches();
3662		let result;
3663		#[block]
3664		{
3665			result = evm::run_plain(&mut interpreter);
3666		}
3667
3668		let ControlFlow::Break(halt) = result;
3669		assert!(matches!(halt, Halt::Stop));
3670		assert_eq!(interpreter.stack.len(), 0);
3671	}
3672
3673	/// Benchmarks `r` EVM `MOD` op-codes.
3674	///
3675	/// # Added Overheads
3676	///
3677	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3678	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3679	///   the divisor is picked at random from [`knuth_division_operands`].
3680	#[benchmark(pov_mode = Measured)]
3681	fn evm_mod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3682		use knuth_division_operands::DENOMINATORS;
3683
3684		let mut rng = Pcg64::seed_from_u64(1337);
3685		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3686
3687		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3688		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3689		let (mut ext, _) = setup.ext();
3690		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3691		setup_stack(&mut interpreter, operands);
3692
3693		evict_caches();
3694		let result;
3695		#[block]
3696		{
3697			result = evm::run_plain(&mut interpreter);
3698		}
3699
3700		let ControlFlow::Break(halt) = result;
3701		assert!(matches!(halt, Halt::Stop));
3702		assert_eq!(interpreter.stack.len(), 0);
3703	}
3704
3705	/// Benchmarks `r` EVM `MOD` op-codes with pseudo-random one-limb correction patterns.
3706	#[benchmark(pov_mode = Measured)]
3707	fn evm_mod_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3708		use one_limb_division_operands::DENOMINATORS;
3709
3710		let mut rng = Pcg64::seed_from_u64(1337);
3711		let operands = (0..r).flat_map(|_| unsigned_division_operands(&mut rng, &DENOMINATORS));
3712
3713		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3714		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3715		let (mut ext, _) = setup.ext();
3716		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3717		setup_stack(&mut interpreter, operands);
3718
3719		evict_caches();
3720		let result;
3721		#[block]
3722		{
3723			result = evm::run_plain(&mut interpreter);
3724		}
3725
3726		let ControlFlow::Break(halt) = result;
3727		assert!(matches!(halt, Halt::Stop));
3728		assert_eq!(interpreter.stack.len(), 0);
3729	}
3730
3731	/// Benchmarks `r` EVM `MOD` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3732	#[benchmark(pov_mode = Measured)]
3733	fn evm_mod_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3734		let mut rng = Pcg64::seed_from_u64(1337);
3735		let operands = (0..r).flat_map(|_| mixed_division_operands::unsigned_operands(&mut rng));
3736
3737		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3738		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3739		let (mut ext, _) = setup.ext();
3740		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3741		setup_stack(&mut interpreter, operands);
3742
3743		evict_caches();
3744		let result;
3745		#[block]
3746		{
3747			result = evm::run_plain(&mut interpreter);
3748		}
3749
3750		let ControlFlow::Break(halt) = result;
3751		assert!(matches!(halt, Halt::Stop));
3752		assert_eq!(interpreter.stack.len(), 0);
3753	}
3754
3755	/// Benchmarks `r` EVM `MOD` op-codes with the selected fixed Knuth divisor.
3756	#[benchmark(pov_mode = Measured)]
3757	fn evm_mod_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3758		use knuth_division_operands::{MOD_FIXED_DIVISOR, NUMERATOR};
3759
3760		let operands = (0..r).flat_map(|_| [MOD_FIXED_DIVISOR, NUMERATOR]);
3761		let code = Bytecode::new_raw([MOD, POP].repeat(r as usize).into());
3762		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3763		let (mut ext, _) = setup.ext();
3764		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3765		setup_stack(&mut interpreter, operands);
3766
3767		evict_caches();
3768		let result;
3769		#[block]
3770		{
3771			result = evm::run_plain(&mut interpreter);
3772		}
3773
3774		let ControlFlow::Break(halt) = result;
3775		assert!(matches!(halt, Halt::Stop));
3776		assert_eq!(interpreter.stack.len(), 0);
3777	}
3778
3779	/// Benchmarks `r` EVM `SMOD` op-codes.
3780	///
3781	/// # Added Overheads
3782	///
3783	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3784	/// * A carry through every limb to negate the numerator, since it's the most negative value.
3785	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3786	///   the divisor is picked at random from [`knuth_division_operands`].
3787	/// * A branch misprediction on whether the divisor is negated, since its sign is pseudo-random.
3788	#[benchmark(pov_mode = Measured)]
3789	fn evm_smod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3790		use knuth_division_operands::DENOMINATORS;
3791
3792		let mut rng = Pcg64::seed_from_u64(1337);
3793		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3794
3795		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3796		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3797		let (mut ext, _) = setup.ext();
3798		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3799		setup_stack(&mut interpreter, operands);
3800
3801		evict_caches();
3802		let result;
3803		#[block]
3804		{
3805			result = evm::run_plain(&mut interpreter);
3806		}
3807
3808		let ControlFlow::Break(halt) = result;
3809		assert!(matches!(halt, Halt::Stop));
3810		assert_eq!(interpreter.stack.len(), 0);
3811	}
3812
3813	/// Benchmarks `r` EVM `SMOD` op-codes with pseudo-random one-limb correction patterns.
3814	#[benchmark(pov_mode = Measured)]
3815	fn evm_smod_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3816		use one_limb_division_operands::DENOMINATORS;
3817
3818		let mut rng = Pcg64::seed_from_u64(1337);
3819		let operands = (0..r).flat_map(|_| signed_division_operands(&mut rng, &DENOMINATORS));
3820
3821		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3822		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3823		let (mut ext, _) = setup.ext();
3824		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3825		setup_stack(&mut interpreter, operands);
3826
3827		evict_caches();
3828		let result;
3829		#[block]
3830		{
3831			result = evm::run_plain(&mut interpreter);
3832		}
3833
3834		let ControlFlow::Break(halt) = result;
3835		assert!(matches!(halt, Halt::Stop));
3836		assert_eq!(interpreter.stack.len(), 0);
3837	}
3838
3839	/// Benchmarks `r` EVM `SMOD` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3840	#[benchmark(pov_mode = Measured)]
3841	fn evm_smod_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3842		let mut rng = Pcg64::seed_from_u64(1337);
3843		let operands = (0..r).flat_map(|_| mixed_division_operands::signed_operands(&mut rng));
3844
3845		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3846		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3847		let (mut ext, _) = setup.ext();
3848		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3849		setup_stack(&mut interpreter, operands);
3850
3851		evict_caches();
3852		let result;
3853		#[block]
3854		{
3855			result = evm::run_plain(&mut interpreter);
3856		}
3857
3858		let ControlFlow::Break(halt) = result;
3859		assert!(matches!(halt, Halt::Stop));
3860		assert_eq!(interpreter.stack.len(), 0);
3861	}
3862
3863	/// Benchmarks `r` EVM `SMOD` op-codes with the selected fixed Knuth divisor.
3864	#[benchmark(pov_mode = Measured)]
3865	fn evm_smod_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 2 }>) {
3866		use knuth_division_operands::{SMOD_FIXED_DIVISOR, fixed_signed_operands};
3867
3868		let mut rng = Pcg64::seed_from_u64(1337);
3869		let operands = (0..r).flat_map(|_| fixed_signed_operands(&mut rng, SMOD_FIXED_DIVISOR));
3870
3871		let code = Bytecode::new_raw([SMOD, POP].repeat(r as usize).into());
3872		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3873		let (mut ext, _) = setup.ext();
3874		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3875		setup_stack(&mut interpreter, operands);
3876
3877		evict_caches();
3878		let result;
3879		#[block]
3880		{
3881			result = evm::run_plain(&mut interpreter);
3882		}
3883
3884		let ControlFlow::Break(halt) = result;
3885		assert!(matches!(halt, Halt::Stop));
3886		assert_eq!(interpreter.stack.len(), 0);
3887	}
3888
3889	/// Benchmarks `r` EVM `ADDMOD` op-codes.
3890	///
3891	/// # Added Overheads
3892	///
3893	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
3894	/// * A division for each addend, since both are above the modulus.
3895	/// * A branch misprediction on how many times each quotient limb's estimate is corrected, since
3896	///   the modulus is picked at random from [`knuth_division_operands`].
3897	/// * A branch misprediction on whether the modulus is subtracted from the sum, since that
3898	///   depends on the modulus picked.
3899	#[benchmark(pov_mode = Measured)]
3900	fn evm_addmod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3901		use knuth_division_operands::DENOMINATORS;
3902
3903		let mut rng = Pcg64::seed_from_u64(1337);
3904		let operands = (0..r).flat_map(|_| {
3905			let [modulus, numerator] = unsigned_division_operands(&mut rng, &DENOMINATORS);
3906			[modulus, numerator, numerator]
3907		});
3908
3909		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3910		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3911		let (mut ext, _) = setup.ext();
3912		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3913		setup_stack(&mut interpreter, operands);
3914
3915		evict_caches();
3916		let result;
3917		#[block]
3918		{
3919			result = evm::run_plain(&mut interpreter);
3920		}
3921
3922		let ControlFlow::Break(halt) = result;
3923		assert!(matches!(halt, Halt::Stop));
3924		assert_eq!(interpreter.stack.len(), 0);
3925	}
3926
3927	/// Benchmarks `r` EVM `ADDMOD` op-codes with pseudo-random one-limb correction patterns.
3928	#[benchmark(pov_mode = Measured)]
3929	fn evm_addmod_opcode_one_limb_variant(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3930		use one_limb_division_operands::DENOMINATORS;
3931
3932		let mut rng = Pcg64::seed_from_u64(1337);
3933		let operands = (0..r).flat_map(|_| {
3934			let [modulus, numerator] = unsigned_division_operands(&mut rng, &DENOMINATORS);
3935			[modulus, numerator, numerator]
3936		});
3937
3938		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3939		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3940		let (mut ext, _) = setup.ext();
3941		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3942		setup_stack(&mut interpreter, operands);
3943
3944		evict_caches();
3945		let result;
3946		#[block]
3947		{
3948			result = evm::run_plain(&mut interpreter);
3949		}
3950
3951		let ControlFlow::Break(halt) = result;
3952		assert!(matches!(halt, Halt::Stop));
3953		assert_eq!(interpreter.stack.len(), 0);
3954	}
3955
3956	/// Benchmarks `r` EVM `ADDMOD` op-codes with pseudo-random mixed one-limb and Knuth divisors.
3957	#[benchmark(pov_mode = Measured)]
3958	fn evm_addmod_opcode_mixed_variant(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3959		let mut rng = Pcg64::seed_from_u64(1337);
3960		let operands = (0..r).flat_map(|_| {
3961			let [modulus, numerator] = mixed_division_operands::unsigned_operands(&mut rng);
3962			[modulus, numerator, numerator]
3963		});
3964
3965		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3966		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3967		let (mut ext, _) = setup.ext();
3968		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3969		setup_stack(&mut interpreter, operands);
3970
3971		evict_caches();
3972		let result;
3973		#[block]
3974		{
3975			result = evm::run_plain(&mut interpreter);
3976		}
3977
3978		let ControlFlow::Break(halt) = result;
3979		assert!(matches!(halt, Halt::Stop));
3980		assert_eq!(interpreter.stack.len(), 0);
3981	}
3982
3983	/// Benchmarks `r` EVM `ADDMOD` op-codes with the selected fixed Knuth divisor.
3984	#[benchmark(pov_mode = Measured)]
3985	fn evm_addmod_opcode_fixed_knuth_variant(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
3986		use knuth_division_operands::{ADDMOD_FIXED_DIVISOR, NUMERATOR};
3987
3988		let operands = (0..r).flat_map(|_| [ADDMOD_FIXED_DIVISOR, NUMERATOR, NUMERATOR]);
3989		let code = Bytecode::new_raw([ADDMOD, POP].repeat(r as usize).into());
3990		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
3991		let (mut ext, _) = setup.ext();
3992		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
3993		setup_stack(&mut interpreter, operands);
3994
3995		evict_caches();
3996		let result;
3997		#[block]
3998		{
3999			result = evm::run_plain(&mut interpreter);
4000		}
4001
4002		let ControlFlow::Break(halt) = result;
4003		assert!(matches!(halt, Halt::Stop));
4004		assert_eq!(interpreter.stack.len(), 0);
4005	}
4006
4007	/// Benchmarks `r` EVM `MULMOD` op-codes.
4008	///
4009	/// # Added Overheads
4010	///
4011	/// * Cold reads, since the L1 and L2 caches are evicted before the benchmark runs.
4012	/// * A product that fills every limb, since the top and lowest bits of both operands are set.
4013	/// * A multiply-subtract over all divisor limbs instead of all but the top two, since the top
4014	///   bits of the modulus are clear and division requires a nonzero normalization shift.
4015	/// * A branch misprediction on how many times the division loops, since the size of the modulus
4016	///   is pseudo-random.
4017	/// * A branch misprediction on each adjustment of the modulus's reciprocal, since whether it
4018	///   happens is pseudo-random.
4019	/// * A branch misprediction on the carry steps of the multiplication, since which limbs of one
4020	///   operand are zero is pseudo-random.
4021	#[benchmark(pov_mode = Measured)]
4022	fn evm_mulmod_opcode(r: Linear<0, { EVM_STACK_LIMIT / 3 }>) {
4023		let top_bit = U256::one() << 255;
4024		// The division computes the reciprocal of the top two limbs of the shifted modulus from the
4025		// top limb first, and then adjusts it twice for the second limb. These say whether each
4026		// adjustment happens.
4027		let reciprocal_adjustments = |modulus: U256| {
4028			let top = (modulus << modulus.leading_zeros() as usize) >> 128;
4029			let (high, low) = (top.low_u128() >> 64, u128::from(top.low_u64()));
4030			[low > u128::MAX % high, (U256::MAX >> 64) / top < U256::from((u128::MAX - low) / high)]
4031		};
4032		let mut rng = Pcg64::seed_from_u64(1337);
4033		let operands = (0..r).flat_map(|_| {
4034			let limbs = if rng.gen_bool(0.5) { 3 } else { 4 };
4035			let adjustments = [rng.gen_bool(0.5), rng.gen_bool(0.5)];
4036			let modulus = loop {
4037				let modulus = (U256(rng.r#gen()) | top_bit) >> (2 + 64 * (4 - limbs));
4038				if reciprocal_adjustments(modulus) == adjustments {
4039					break modulus;
4040				}
4041			};
4042			let a = U256(rng.r#gen()) | top_bit | U256::one();
4043			let mut b = U256(rng.r#gen()) | top_bit | U256::one();
4044			for limb in 1..=2 {
4045				if rng.gen_bool(0.5) {
4046					b.0[limb] = 0;
4047				}
4048			}
4049			[modulus, b, a]
4050		});
4051
4052		let code = Bytecode::new_raw([MULMOD, POP].repeat(r as usize).into());
4053		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_noop(0));
4054		let (mut ext, _) = setup.ext();
4055		let mut interpreter = Interpreter::new(ExtBytecode::new(code), Vec::new(), &mut ext);
4056		setup_stack(&mut interpreter, operands);
4057
4058		evict_caches();
4059		let result;
4060		#[block]
4061		{
4062			result = evm::run_plain(&mut interpreter);
4063		}
4064
4065		let ControlFlow::Break(halt) = result;
4066		assert!(matches!(halt, Halt::Stop));
4067		assert_eq!(interpreter.stack.len(), 0);
4068	}
4069
4070	// Benchmark the execution of instructions.
4071	//
4072	// It benchmarks the absolute worst case by allocating a lot of memory
4073	// and then accessing it so that each instruction generates two cache misses.
4074	#[benchmark(pov_mode = Ignored)]
4075	fn instr(r: Linear<0, 10_000>) {
4076		use rand::{SeedableRng, seq::SliceRandom};
4077		use rand_pcg::Pcg64;
4078
4079		// Ideally, this needs to be bigger than the cache.
4080		const MEMORY_SIZE: u64 = sp_core::MAX_POSSIBLE_ALLOCATION as u64;
4081
4082		// This is benchmarked for x86-64.
4083		const CACHE_LINE_SIZE: u64 = 64;
4084
4085		// An 8 byte load from this misalignment will reach into the subsequent line.
4086		const MISALIGNMENT: u64 = 60;
4087
4088		// We only need one address per cache line.
4089		// -1 because we skip the first address
4090		const NUM_ADDRESSES: u64 = (MEMORY_SIZE - MISALIGNMENT) / CACHE_LINE_SIZE - 1;
4091
4092		assert!(
4093			u64::from(r) <= NUM_ADDRESSES / 2,
4094			"If we do too many iterations we run into the risk of loading from warm cache lines",
4095		);
4096
4097		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(true));
4098		let (mut ext, module) = setup.ext();
4099		let mut prepared =
4100			CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), MEMORY_SIZE as u32);
4101
4102		assert!(
4103			u64::from(prepared.aux_data_base()) & (CACHE_LINE_SIZE - 1) == 0,
4104			"aux data base must be cache aligned"
4105		);
4106
4107		// Addresses data will be located inside the aux data.
4108		let misaligned_base = u64::from(prepared.aux_data_base()) + MISALIGNMENT;
4109
4110		// Create all possible addresses and shuffle them. This makes sure
4111		// the accesses are random but no address is accessed more than once.
4112		// we skip the first address since it is our entry point
4113		let mut addresses = Vec::with_capacity(NUM_ADDRESSES as usize);
4114		for i in 1..NUM_ADDRESSES {
4115			let addr = (misaligned_base + i * CACHE_LINE_SIZE).to_le_bytes();
4116			addresses.push(addr);
4117		}
4118		let mut rng = Pcg64::seed_from_u64(1337);
4119		addresses.shuffle(&mut rng);
4120
4121		// The addresses need to be padded to be one cache line apart.
4122		let mut memory = Vec::with_capacity((NUM_ADDRESSES * CACHE_LINE_SIZE) as usize);
4123		for address in addresses {
4124			memory.extend_from_slice(&address);
4125			memory.resize(memory.len() + CACHE_LINE_SIZE as usize - address.len(), 0);
4126		}
4127
4128		// Copies `memory` to `aux_data_base + MISALIGNMENT`.
4129		// Sets `a0 = MISALIGNMENT` and `a1 = r`.
4130		prepared
4131			.setup_aux_data(memory.as_slice(), MISALIGNMENT as u32, r.into())
4132			.unwrap();
4133
4134		#[block]
4135		{
4136			prepared.call().unwrap();
4137		}
4138	}
4139
4140	#[benchmark(pov_mode = Ignored)]
4141	fn instr_empty_loop(r: Linear<0, 10_000>) {
4142		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(false));
4143		let (mut ext, module) = setup.ext();
4144		let mut prepared = CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), 0);
4145		prepared.setup_aux_data(&[], 0, r.into()).unwrap();
4146
4147		#[block]
4148		{
4149			prepared.call().unwrap();
4150		}
4151	}
4152
4153	#[benchmark(pov_mode = Measured)]
4154	fn extcodecopy(n: Linear<1_000, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
4155		// The caller contract; `CallSetup` whitelists its `AccountInfoOf`.
4156		let mut setup = CallSetup::<T>::new(VmBinaryModule::dummy());
4157		// Copy a contract other than the caller, so its `AccountInfoOf` read is counted.
4158		let target = Contract::<T>::with_index(1, VmBinaryModule::sized(n), vec![])?;
4159
4160		let (mut ext, _) = setup.ext();
4161		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
4162
4163		// Setup stack for extcodecopy instruction: [address, dest_offset, offset, size]
4164		let _ = interpreter.stack.push(U256::from(n));
4165		let _ = interpreter.stack.push(U256::from(0u32));
4166		let _ = interpreter.stack.push(U256::from(0u32));
4167		let _ = interpreter.stack.push(target.address);
4168
4169		let result;
4170		#[block]
4171		{
4172			result = instructions::host::extcodecopy(&mut interpreter);
4173		}
4174
4175		assert!(result.is_continue());
4176		assert_eq!(
4177			*interpreter.memory.slice(0..n as usize),
4178			PristineCode::<T>::get(target.info()?.code_hash).unwrap()[0..n as usize],
4179			"Memory should contain the target contract's code after extcodecopy"
4180		);
4181
4182		Ok(())
4183	}
4184
4185	#[benchmark]
4186	fn v1_migration_step() {
4187		use crate::migrations::v1;
4188		let addr = H160::from([1u8; 20]);
4189		let contract_info = ContractInfo::new(&addr, 1u32.into(), Default::default()).unwrap();
4190
4191		v1::old::ContractInfoOf::<T>::insert(addr, contract_info.clone());
4192		let mut meter = WeightMeter::new();
4193		assert_eq!(AccountInfo::<T>::load_contract(&addr), None);
4194
4195		#[block]
4196		{
4197			v1::Migration::<T>::step(None, &mut meter).unwrap();
4198		}
4199
4200		assert_eq!(v1::old::ContractInfoOf::<T>::get(&addr), None);
4201		assert_eq!(AccountInfo::<T>::load_contract(&addr).unwrap(), contract_info);
4202
4203		// uses twice the weight once for migration and then for checking if there is another key.
4204		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v1_migration_step() * 2);
4205	}
4206
4207	#[benchmark]
4208	fn v2_migration_step() {
4209		use crate::migrations::v2;
4210		let code_hash = H256::from([0; 32]);
4211		let old_code_info = v2::Migration::<T>::create_old_code_info(
4212			whitelisted_caller(),
4213			1000u32.into(),
4214			1,
4215			100,
4216			0,
4217		);
4218		v2::Migration::<T>::insert_old_code_info(code_hash, old_code_info.clone());
4219		let mut meter = WeightMeter::new();
4220
4221		#[block]
4222		{
4223			v2::Migration::<T>::step(None, &mut meter).unwrap();
4224		}
4225
4226		v2::Migration::<T>::assert_migrated_code_info(code_hash, &old_code_info);
4227
4228		// uses twice the weight once for migration and then for checking if there is another key.
4229		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v2_migration_step() * 2);
4230	}
4231
4232	#[benchmark]
4233	fn v3_migration_step() {
4234		use crate::migrations::v3;
4235		// Remove all pre-existing accounts
4236		let _ = frame_system::Account::<T>::clear(u32::MAX, None);
4237
4238		let account = account::<T::AccountId>("target", 0, 0);
4239		T::Currency::mint_into(&account, Pallet::<T>::min_balance())
4240			.expect("should mint into account");
4241
4242		// clear the mapping so the migration has work to do
4243		let addr = T::AddressMapper::to_address(&account);
4244		crate::OriginalAccount::<T>::remove(addr);
4245
4246		assert!(!T::AddressMapper::is_mapped(&account));
4247		let mut meter = WeightMeter::new();
4248
4249		#[block]
4250		{
4251			v3::Migration::<T>::step(None, &mut meter).unwrap();
4252		}
4253
4254		assert!(T::AddressMapper::is_mapped(&account));
4255
4256		// uses twice the weight: once for migration and then for checking if there is another key.
4257		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v3_migration_step() * 2);
4258	}
4259
4260	/// One iteration of v4 phase 1: credit the uploader's [`NativeDepositOf`] bucket.
4261	///
4262	/// Seeds two codes and primes the cursor with the first stored entry so the benched
4263	/// iteration exercises the `iter_from` path that dominates phase 1 in production.
4264	#[benchmark]
4265	fn v4_code_upload_step() {
4266		use crate::migrations::v4;
4267
4268		let _ = CodeInfoOf::<T>::clear(u32::MAX, None);
4269
4270		let owner: T::AccountId = whitelisted_caller();
4271		let deposit: BalanceOf<T> = 1_000u32.into();
4272
4273		let pallet_account = Pallet::<T>::account_id();
4274		T::Currency::mint_into(&pallet_account, Pallet::<T>::min_balance()).unwrap();
4275		T::Currency::mint_into(&pallet_account, deposit).unwrap();
4276		T::Currency::hold(&HoldReason::CodeUploadDepositReserve.into(), &pallet_account, deposit)
4277			.unwrap();
4278
4279		CodeInfoOf::<T>::insert(
4280			H256::from([1u8; 32]),
4281			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
4282		);
4283		CodeInfoOf::<T>::insert(
4284			H256::from([2u8; 32]),
4285			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
4286		);
4287
4288		let first = match v4::Migration::<T>::step_once(None) {
4289			Some(v4::Cursor::CodeUpload(h)) => h,
4290			other => panic!("expected CodeUpload cursor, got {other:?}"),
4291		};
4292		let cursor = Some(v4::Cursor::CodeUpload(first));
4293
4294		#[block]
4295		{
4296			let _ = v4::Migration::<T>::step_once(cursor);
4297		}
4298
4299		assert_eq!(
4300			NativeDepositOf::<T>::get(&pallet_account, &owner),
4301			deposit + deposit,
4302			"both code uploads credited to owner",
4303		);
4304	}
4305
4306	/// One iteration of v4 phase 2: burn native hold, mint and hold PGAS for a single contract.
4307	///
4308	/// Seeds two contracts and primes the cursor with the first stored entry so the benched
4309	/// iteration exercises the `iter_from` path that dominates phase 2 in production.
4310	#[benchmark]
4311	fn v4_contract_step() {
4312		use crate::migrations::v4;
4313
4314		let _ = AccountInfoOf::<T>::clear(u32::MAX, None);
4315
4316		let code_hash = H256::from([0u8; 32]);
4317		let deposit: BalanceOf<T> = 1_000u32.into();
4318
4319		for byte in [0x41u8, 0x42u8] {
4320			let addr = H160::from([byte; 20]);
4321			let contract_account = T::AddressMapper::to_account_id(&addr);
4322			let info =
4323				ContractInfo::<T>::new(&addr, 1u32.into(), code_hash).expect("fresh contract info");
4324			AccountInfoOf::<T>::insert(
4325				addr,
4326				crate::storage::AccountInfo::<T> {
4327					account_type: crate::storage::AccountType::Contract(info),
4328					dust: 0,
4329				},
4330			);
4331			T::Currency::mint_into(&contract_account, Pallet::<T>::min_balance()).unwrap();
4332			T::Currency::mint_into(&contract_account, deposit).unwrap();
4333			T::Currency::hold(
4334				&HoldReason::StorageDepositReserve.into(),
4335				&contract_account,
4336				deposit,
4337			)
4338			.unwrap();
4339		}
4340
4341		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::Contract(None))) {
4342			Some(v4::Cursor::Contract(Some(addr))) => addr,
4343			other => panic!("expected Contract cursor, got {other:?}"),
4344		};
4345		let cursor = Some(v4::Cursor::Contract(Some(first)));
4346
4347		#[block]
4348		{
4349			let _ = v4::Migration::<T>::step_once(cursor);
4350		}
4351
4352		// `migrate_native_to_pgas` is a no-op for `Deposit = ()`, so the hold only clears on
4353		// PGAS-backed runtimes. On non-PGAS runtimes the benchmark still measures the iter cost.
4354		if T::Deposit::SUPPORTS_PGAS {
4355			for byte in [0x41u8, 0x42u8] {
4356				let addr = H160::from([byte; 20]);
4357				let contract_account = T::AddressMapper::to_account_id(&addr);
4358				assert_eq!(
4359					T::Currency::balance_on_hold(
4360						&HoldReason::StorageDepositReserve.into(),
4361						&contract_account,
4362					),
4363					0u32.into(),
4364					"native storage deposit burned for {addr:?}",
4365				);
4366			}
4367		}
4368	}
4369
4370	/// One iteration of v4 phase 3: rewrite a legacy [`v4::old::DeletionQueue`] entry into the
4371	/// new [`DeletionQueue`] format.
4372	///
4373	/// Seeds two legacy entries and primes the cursor with the first stored entry so the benched
4374	/// iteration exercises the `iter_from` path.
4375	#[benchmark]
4376	fn v4_deletion_queue_step() {
4377		use crate::migrations::v4;
4378
4379		let _ = v4::old::DeletionQueue::<T>::clear(u32::MAX, None);
4380
4381		let trie_a: TrieId = vec![0xAAu8; 16].try_into().unwrap();
4382		let trie_b: TrieId = vec![0xBBu8; 24].try_into().unwrap();
4383		v4::old::DeletionQueue::<T>::insert(0u32, trie_a);
4384		v4::old::DeletionQueue::<T>::insert(1u32, trie_b);
4385
4386		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::DeletionQueue(None))) {
4387			Some(v4::Cursor::DeletionQueue(Some(key))) => key,
4388			other => panic!("expected DeletionQueue cursor, got {other:?}"),
4389		};
4390		let cursor = Some(v4::Cursor::DeletionQueue(Some(first)));
4391
4392		#[block]
4393		{
4394			let _ = v4::Migration::<T>::step_once(cursor);
4395		}
4396
4397		assert!(
4398			DeletionQueue::<T>::get(0u32).is_some() && DeletionQueue::<T>::get(1u32).is_some(),
4399			"both legacy entries rewritten into the new format",
4400		);
4401	}
4402
4403	/// Helper function to create a test signer for finalize_block benchmark
4404	fn create_test_signer<T: Config>() -> (T::AccountId, SigningKey, H160) {
4405		use hex_literal::hex;
4406		// dev::alith()
4407		let signer_account_id = hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac");
4408		let signer_priv_key =
4409			hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133");
4410
4411		let signer_key = SigningKey::from_bytes(&signer_priv_key.into()).expect("valid key");
4412
4413		let signer_address = H160::from_slice(&signer_account_id);
4414		let signer_caller = T::AddressMapper::to_fallback_account_id(&signer_address);
4415
4416		(signer_caller, signer_key, signer_address)
4417	}
4418
4419	/// Helper function to create and sign a transaction for finalize_block benchmark
4420	fn create_signed_transaction<T: Config>(
4421		signer_key: &SigningKey,
4422		target_address: H160,
4423		value: U256,
4424		input_data: Vec<u8>,
4425	) -> Vec<u8> {
4426		let unsigned_tx: TransactionUnsigned = TransactionLegacyUnsigned {
4427			to: Some(target_address),
4428			value,
4429			chain_id: Some(T::ChainId::get().into()),
4430			input: input_data.into(),
4431			..Default::default()
4432		}
4433		.into();
4434
4435		let hashed_payload = sp_io::hashing::keccak_256(&unsigned_tx.unsigned_payload());
4436		let (signature, recovery_id) =
4437			signer_key.sign_prehash_recoverable(&hashed_payload).expect("signing success");
4438
4439		let mut sig_bytes = [0u8; 65];
4440		sig_bytes[..64].copy_from_slice(&signature.to_bytes());
4441		sig_bytes[64] = recovery_id.to_byte();
4442
4443		let signed_tx = unsigned_tx.with_signature(sig_bytes);
4444
4445		signed_tx.signed_payload()
4446	}
4447
4448	/// Helper function to generate common finalize_block benchmark setup
4449	fn setup_finalize_block_benchmark<T>()
4450	-> Result<(Contract<T>, BalanceOf<T>, U256, SigningKey, BlockNumberFor<T>), BenchmarkError>
4451	where
4452		BalanceOf<T>: Into<U256> + TryFrom<U256>,
4453		T: Config,
4454		MomentOf<T>: Into<U256>,
4455		<T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
4456	{
4457		// Setup test signer
4458		let (signer_caller, signer_key, _signer_address) = create_test_signer::<T>();
4459		whitelist_account!(signer_caller);
4460
4461		// Setup contract instance
4462		let instance =
4463			Contract::<T>::with_caller(signer_caller.clone(), VmBinaryModule::dummy(), vec![])?;
4464		let storage_deposit = default_deposit_limit::<T>();
4465		let value = Pallet::<T>::min_balance();
4466		let evm_value =
4467			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, 0));
4468
4469		// Setup block
4470		let current_block = BlockNumberFor::<T>::from(1u32);
4471		frame_system::Pallet::<T>::set_block_number(current_block);
4472
4473		// Where `Deposit` mints through `fungibles` and the runtime mirrors balance changes as
4474		// logs, creating the contract above buffers one. Drop it, so these benchmarks measure the
4475		// transactions and logs they set up themselves and no synthetic transaction on top.
4476		Pallet::<T>::clear_outside_frame_logs();
4477
4478		Ok((instance, storage_deposit, evm_value, signer_key, current_block))
4479	}
4480
4481	/// Benchmark the `on_finalize` hook scaling with number of transactions.
4482	///
4483	/// This benchmark measures the marginal computational cost of adding transactions
4484	/// to a block during finalization, with fixed payload size to isolate transaction
4485	/// count scaling effects.
4486	///
4487	/// ## Parameters:
4488	/// - `n`: Number of transactions in the block (0-200)
4489	///
4490	/// ## Test Setup:
4491	/// - Creates `n` transactions with fixed 100-byte payloads
4492	/// - Pre-populates block builder storage with test data
4493	///
4494	/// ## Usage:
4495	/// Use this with `on_finalize_per_byte` to calculate total cost:
4496	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
4497	#[benchmark(pov_mode = Measured)]
4498	fn on_finalize_per_transaction(n: Linear<0, 200>) -> Result<(), BenchmarkError> {
4499		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4500			setup_finalize_block_benchmark::<T>()?;
4501
4502		// Fixed payload size to isolate transaction count effects
4503		let fixed_payload_size = 100usize;
4504
4505		// Pre-populate InflightTransactions with n transactions of fixed size
4506		if n > 0 {
4507			// Initialize block
4508			let _ = Pallet::<T>::on_initialize(current_block);
4509
4510			// Create input data of fixed size for consistent transaction payloads
4511			let input_data = vec![0x42u8; fixed_payload_size];
4512			let receipt_gas_info = ReceiptGasInfo {
4513				gas_used: U256::from(1_000_000),
4514				effective_gas_price: Pallet::<T>::evm_base_fee(),
4515			};
4516
4517			for _ in 0..n {
4518				// Create real signed transaction with fixed-size input data
4519				let signed_transaction = create_signed_transaction::<T>(
4520					&signer_key,
4521					instance.address,
4522					evm_value,
4523					input_data.clone(),
4524				);
4525
4526				// Store transaction
4527				let _ = block_storage::bench_with_ethereum_context(|| {
4528					let (encoded_logs, bloom) =
4529						block_storage::get_receipt_details::<T>().unwrap_or_default();
4530
4531					let block_builder_ir = EthBlockBuilderIR::<T>::get();
4532					let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4533
4534					block_builder.process_transaction(
4535						signed_transaction,
4536						true,
4537						receipt_gas_info.clone(),
4538						encoded_logs,
4539						bloom,
4540					);
4541
4542					EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4543				});
4544			}
4545		}
4546
4547		#[block]
4548		{
4549			// Measure only the finalization cost with n transactions of fixed size
4550			let _ = Pallet::<T>::on_finalize(current_block);
4551		}
4552
4553		// Verify transaction count
4554		assert_eq!(Pallet::<T>::eth_block().transactions.len(), n as usize);
4555
4556		Ok(())
4557	}
4558
4559	/// Benchmark the `on_finalize` hook scaling with transaction payload size.
4560	///
4561	/// This benchmark measures the marginal computational cost of processing
4562	/// larger transaction payloads during finalization, with fixed transaction count
4563	/// to isolate payload size scaling effects.
4564	///
4565	/// ## Parameters:
4566	/// - `d`: Payload size per transaction in bytes (0-1000)
4567	///
4568	/// ## Test Setup:
4569	/// - Creates 10 transactions with payload size `d`
4570	/// - Pre-populates block builder storage with test data
4571	///
4572	/// ## Usage:
4573	/// Use this with `on_finalize_per_transaction` to calculate total cost:
4574	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
4575	#[benchmark(pov_mode = Measured)]
4576	fn on_finalize_per_transaction_data(d: Linear<0, 1000>) -> Result<(), BenchmarkError> {
4577		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4578			setup_finalize_block_benchmark::<T>()?;
4579
4580		// Fixed transaction count to isolate payload size effects
4581		let fixed_tx_count = 10u32;
4582
4583		// Initialize block
4584		let _ = Pallet::<T>::on_initialize(current_block);
4585
4586		// Create input data of variable size p for realistic transaction payloads
4587		let input_data = vec![0x42u8; d as usize];
4588		let receipt_gas_info = ReceiptGasInfo {
4589			gas_used: U256::from(1_000_000),
4590			effective_gas_price: Pallet::<T>::evm_base_fee(),
4591		};
4592
4593		for _ in 0..fixed_tx_count {
4594			// Create real signed transaction with variable-size input data
4595			let signed_transaction = create_signed_transaction::<T>(
4596				&signer_key,
4597				instance.address,
4598				evm_value,
4599				input_data.clone(),
4600			);
4601
4602			// Store transaction
4603			let _ = block_storage::bench_with_ethereum_context(|| {
4604				let (encoded_logs, bloom) =
4605					block_storage::get_receipt_details::<T>().unwrap_or_default();
4606
4607				let block_builder_ir = EthBlockBuilderIR::<T>::get();
4608				let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4609
4610				block_builder.process_transaction(
4611					signed_transaction,
4612					true,
4613					receipt_gas_info.clone(),
4614					encoded_logs,
4615					bloom,
4616				);
4617
4618				EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4619			});
4620		}
4621
4622		#[block]
4623		{
4624			// Measure only the finalization cost with fixed count, variable payload size
4625			let _ = Pallet::<T>::on_finalize(current_block);
4626		}
4627
4628		// Verify transaction count
4629		assert_eq!(Pallet::<T>::eth_block().transactions.len(), fixed_tx_count as usize);
4630
4631		Ok(())
4632	}
4633
4634	/// Benchmark the `on_finalize` per-event costs.
4635	///
4636	/// This benchmark measures the computational cost of processing events
4637	/// within the finalization process, isolating the overhead of event count.
4638	/// Uses a single transaction with varying numbers of minimal events.
4639	///
4640	/// ## Parameters:
4641	/// - `e`: Number of events per transaction
4642	///
4643	/// ## Test Setup:
4644	/// - Creates 1 transaction with `e` ContractEmitted events
4645	/// - Each event contains minimal data (no topics, empty data field)
4646	///
4647	/// ## Usage:
4648	/// Measures the per-event processing overhead during finalization
4649	/// - Fixed cost: `on_finalize_per_event(0)` - baseline finalization cost
4650	/// - Per event: `on_finalize_per_event(e)` - linear scaling with event count
4651	#[benchmark(pov_mode = Measured)]
4652	fn on_finalize_per_event(e: Linear<0, 100>) -> Result<(), BenchmarkError> {
4653		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4654			setup_finalize_block_benchmark::<T>()?;
4655
4656		// Create a single transaction with e events, each with minimal data
4657		let input_data = vec![0x42u8; 100];
4658		let signed_transaction = create_signed_transaction::<T>(
4659			&signer_key,
4660			instance.address,
4661			evm_value,
4662			input_data.clone(),
4663		);
4664
4665		let receipt_gas_info = ReceiptGasInfo {
4666			gas_used: U256::from(1_000_000),
4667			effective_gas_price: Pallet::<T>::evm_base_fee(),
4668		};
4669
4670		// Store transaction
4671		let _ = block_storage::bench_with_ethereum_context(|| {
4672			// Captured inside the ethereum context, so each lands on the transaction's own receipt
4673			// — the path `on_finalize_block_per_event` is charged for on every `DepositEvent`. The
4674			// outside-of-frame drain is `outside_frame_log`'s, charged separately.
4675			for _ in 0..e {
4676				block_storage::capture_frame_log::<T>(&instance.address, &vec![], &vec![]);
4677			}
4678
4679			let (encoded_logs, bloom) =
4680				block_storage::get_receipt_details::<T>().unwrap_or_default();
4681
4682			let block_builder_ir = EthBlockBuilderIR::<T>::get();
4683			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4684
4685			block_builder.process_transaction(
4686				signed_transaction,
4687				true,
4688				receipt_gas_info.clone(),
4689				encoded_logs,
4690				bloom,
4691			);
4692
4693			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4694		});
4695
4696		#[block]
4697		{
4698			// Initialize block
4699			let _ = Pallet::<T>::on_initialize(current_block);
4700
4701			// Measure the finalization cost with e events
4702			let _ = Pallet::<T>::on_finalize(current_block);
4703		}
4704
4705		// The real transaction only: its `e` logs are on its own receipt, so no synthetic
4706		// transaction is built.
4707		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
4708
4709		Ok(())
4710	}
4711
4712	/// ## Test Setup:
4713	/// - Creates 1 transaction with 1 ContractEmitted event
4714	/// - Event contains `d` total bytes of data across data field and topics
4715	///
4716	/// ## Usage:
4717	/// Measures the per-byte event data processing overhead during finalization
4718	/// - Fixed cost: `on_finalize_per_event_data(0)` - baseline cost with empty event
4719	/// - Per byte: `on_finalize_per_event_data(d)` - linear scaling with data size
4720	#[benchmark(pov_mode = Measured)]
4721	fn on_finalize_per_event_data(d: Linear<0, 16384>) -> Result<(), BenchmarkError> {
4722		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
4723			setup_finalize_block_benchmark::<T>()?;
4724
4725		// Create a single transaction with one event containing d bytes of data
4726		let input_data = vec![0x42u8; 100];
4727		let signed_transaction = create_signed_transaction::<T>(
4728			&signer_key,
4729			instance.address,
4730			evm_value,
4731			input_data.clone(),
4732		);
4733
4734		let receipt_gas_info = ReceiptGasInfo {
4735			gas_used: U256::from(1_000_000),
4736			effective_gas_price: Pallet::<T>::evm_base_fee(),
4737		};
4738
4739		// Create one event with d bytes of data distributed across topics and data field
4740		let (event_data, topics) = if d < 32 {
4741			// If total data is less than 32 bytes, put all in data field
4742			(vec![0x42u8; d as usize], vec![])
4743		} else {
4744			// Fill topics first, then put remaining bytes in data field
4745			let num_topics = core::cmp::min(limits::NUM_EVENT_TOPICS, d / 32);
4746			let topic_bytes_used = num_topics * 32;
4747			let data_bytes_remaining = d - topic_bytes_used;
4748
4749			// Create topics filled with sequential data
4750			let mut topics = Vec::new();
4751			for topic_index in 0..num_topics {
4752				let topic_data = [topic_index as u8; 32];
4753				topics.push(H256::from(topic_data));
4754			}
4755
4756			// Remaining bytes go to data field
4757			let event_data = vec![0x42u8; data_bytes_remaining as usize];
4758
4759			(event_data, topics)
4760		};
4761
4762		// Store transaction
4763		let _ = block_storage::bench_with_ethereum_context(|| {
4764			// Captured inside the ethereum context, so the log lands on the transaction's own
4765			// receipt — the path `on_finalize_block_per_event` is charged for on every
4766			// `DepositEvent`. The outside-of-frame drain is `outside_frame_log`'s, charged
4767			// separately.
4768			block_storage::capture_frame_log::<T>(&instance.address, &event_data, &topics);
4769
4770			let (encoded_logs, bloom) =
4771				block_storage::get_receipt_details::<T>().unwrap_or_default();
4772
4773			let block_builder_ir = EthBlockBuilderIR::<T>::get();
4774			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
4775
4776			block_builder.process_transaction(
4777				signed_transaction,
4778				true,
4779				receipt_gas_info,
4780				encoded_logs,
4781				bloom,
4782			);
4783
4784			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
4785		});
4786
4787		#[block]
4788		{
4789			// Initialize block
4790			let _ = Pallet::<T>::on_initialize(current_block);
4791
4792			// Measure the finalization cost with d bytes of event data
4793			let _ = Pallet::<T>::on_finalize(current_block);
4794		}
4795
4796		// The real transaction only: its log is on its own receipt, so no synthetic transaction is
4797		// built.
4798		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
4799
4800		Ok(())
4801	}
4802
4803	/// Benchmark the `on_finalize` drain of `n` buffered outside-of-frame logs: taking the buffer
4804	/// and folding each log into the synthetic transaction's receipt (RLP + bloom).
4805	///
4806	/// `pov_mode = Measured` because the block builder's storage is unbounded. The buffer itself is
4807	/// whitelisted, so the marginal carries no proof size; a log's proof charge is its entry's
4808	/// bytes, registered where it is buffered. The append is in the emitting pallet's weight, see
4809	/// `buffer_outside_frame_log`.
4810	///
4811	/// Each log uses a representative ERC-20 `Transfer` payload: three 32-byte topics and a 32-byte
4812	/// data word.
4813	#[benchmark(pov_mode = Measured)]
4814	fn outside_frame_log(n: Linear<0, 100>) -> Result<(), BenchmarkError> {
4815		let (instance, _storage_deposit, _evm_value, _signer_key, current_block) =
4816			setup_finalize_block_benchmark::<T>()?;
4817
4818		// Realistic order: `on_initialize` runs before the block's extrinsics buffer any logs.
4819		let _ = Pallet::<T>::on_initialize(current_block);
4820
4821		// Representative ERC-20 `Transfer`: topics = [event sig, from, to], data = value word.
4822		let topics =
4823			vec![H256::repeat_byte(0x11), H256::repeat_byte(0x22), H256::repeat_byte(0x33)];
4824		let data = vec![0x44u8; 32];
4825
4826		// Appended straight into the buffer, past the cap: the drain is what is measured, and a
4827		// runtime that keeps the buffer off still needs its weight.
4828		for _ in 0..n {
4829			Pallet::<T>::bench_buffer_outside_frame_log(
4830				instance.address,
4831				topics.clone(),
4832				data.clone(),
4833			);
4834		}
4835
4836		#[block]
4837		{
4838			let _ = Pallet::<T>::on_finalize(current_block);
4839		}
4840
4841		// Only the synthetic transaction, and only when at least one log was buffered.
4842		assert_eq!(Pallet::<T>::eth_block().transactions.len(), (n > 0) as usize);
4843
4844		Ok(())
4845	}
4846
4847	/// Benchmark the `on_finalize` drain of one buffered outside-of-frame log carrying `d` bytes
4848	/// of data: the `OutsideFrameLogs::take` scales with the value it reads back, and so do the
4849	/// RLP encoding and bloom accrual that fold it into the synthetic transaction's receipt.
4850	///
4851	/// Pairs with `outside_frame_log`, which fixes the payload and varies the count; together they
4852	/// give `OnFinalizeBlockParts::per_outside_frame_log` its per-log and per-byte marginals. A
4853	/// contract `LOG` emitted off the ethereum path can carry up to `EVENT_BYTES`, so the count
4854	/// benchmark's 32-byte word alone would under-charge it.
4855	#[benchmark(pov_mode = Measured)]
4856	fn outside_frame_log_data(d: Linear<0, { limits::EVENT_BYTES }>) -> Result<(), BenchmarkError> {
4857		let (instance, _storage_deposit, _evm_value, _signer_key, current_block) =
4858			setup_finalize_block_benchmark::<T>()?;
4859
4860		let _ = Pallet::<T>::on_initialize(current_block);
4861
4862		let topics =
4863			vec![H256::repeat_byte(0x11), H256::repeat_byte(0x22), H256::repeat_byte(0x33)];
4864		let data = vec![0x44u8; d as usize];
4865
4866		Pallet::<T>::bench_buffer_outside_frame_log(instance.address, topics, data);
4867
4868		#[block]
4869		{
4870			let _ = Pallet::<T>::on_finalize(current_block);
4871		}
4872
4873		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
4874
4875		Ok(())
4876	}
4877
4878	impl_benchmark_test_suite!(
4879		Contracts,
4880		crate::tests::ExtBuilder::default().build(),
4881		crate::tests::Test,
4882	);
4883}