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},
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::{Key, Origin as ExecOrigin, PrecompileExt},
30	limits,
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::{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 frame_benchmarking::v2::*;
51use frame_support::{
52	self, assert_ok,
53	migrations::SteppedMigration,
54	storage::child,
55	traits::{Hooks, fungible::InspectHold},
56	weights::{Weight, WeightMeter},
57};
58use frame_system::RawOrigin;
59use k256::ecdsa::SigningKey;
60use pallet_revive_uapi::{
61	CallFlags, ReturnErrorCode, StorageFlags, pack_hi_lo,
62	precompiles::{storage::IStorage, system::ISystem},
63};
64use revm::bytecode::Bytecode;
65use sp_consensus_aura::AURA_ENGINE_ID;
66use sp_consensus_babe::{
67	BABE_ENGINE_ID,
68	digests::{PreDigest, PrimaryPreDigest},
69};
70use sp_consensus_slots::Slot;
71use sp_runtime::{generic::DigestItem, traits::Zero};
72
73/// How many runs we do per API benchmark.
74///
75/// This is picked more or less arbitrary. We experimented with different numbers until
76/// the results appeared to be stable. Reducing the number would speed up the benchmarks
77/// but might make the results less precise.
78const API_BENCHMARK_RUNS: u32 = 1600;
79
80macro_rules! memory(
81	($($bytes:expr,)*) => {{
82		vec![].iter()$(.chain($bytes.iter()))*.cloned().collect::<Vec<_>>()
83	}};
84);
85
86macro_rules! build_runtime(
87	($runtime:ident, $memory:ident: [$($segment:expr,)*]) => {
88		build_runtime!($runtime, _contract, $memory: [$($segment,)*]);
89	};
90	($runtime:ident, $contract:ident, $memory:ident: [$($bytes:expr,)*]) => {
91		build_runtime!($runtime, $contract);
92		let mut $memory = memory!($($bytes,)*);
93	};
94	($runtime:ident, $contract:ident) => {
95		let mut setup = CallSetup::<T>::default();
96		let $contract = setup.contract();
97		let input = setup.data();
98		let (mut ext, _) = setup.ext();
99		let mut $runtime = $crate::vm::pvm::Runtime::<_, [u8]>::new(&mut ext, input);
100	};
101);
102
103/// Get the pallet account and whitelist it for benchmarking.
104/// The account is warmed up `on_initialize` so read should not impact the PoV.
105fn whitelisted_pallet_account<T: Config>() -> T::AccountId {
106	let pallet_account = Pallet::<T>::account_id();
107	whitelist_account!(pallet_account);
108	pallet_account
109}
110
111#[benchmarks(
112	where
113		T: Config,
114		<T as Config>::RuntimeCall: From<frame_system::Call<T>>,
115		<T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
116		OriginFor<T>: From<Origin<T>>,
117)]
118mod benchmarks {
119	use super::*;
120
121	/// The base weight consumed on processing contracts deletion queue.
122	#[benchmark(pov_mode = Measured)]
123	fn deletion_queue_batch() {
124		#[block]
125		{
126			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
127		}
128	}
129
130	/// Measures the per-entry cost of `process_deletion_queue_batch`: one `DeletionQueue` read
131	/// plus the `DeletionQueue` + `DeletionQueueCounter` writes done by `entry.remove()`.
132	#[benchmark(pov_mode = Measured)]
133	fn deletion_queue_per_entry() -> Result<(), BenchmarkError> {
134		let instance = Contract::<T>::with_storage(VmBinaryModule::dummy(), 0, 0)?;
135		ContractInfo::<T>::queue_for_deletion(
136			instance.info()?.trie_id,
137			instance.account_id.clone(),
138		);
139
140		#[block]
141		{
142			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
143		}
144
145		assert!(<DeletionQueue<T>>::iter().next().is_none(), "deletion queue should be drained",);
146		Ok(())
147	}
148
149	#[benchmark(skip_meta, pov_mode = Measured)]
150	fn deletion_queue_per_trie_key(k: Linear<0, 1024>) -> Result<(), BenchmarkError> {
151		let instance =
152			Contract::<T>::with_storage(VmBinaryModule::dummy(), k, limits::STORAGE_BYTES)?;
153		ContractInfo::<T>::queue_for_deletion(
154			instance.info()?.trie_id,
155			instance.account_id.clone(),
156		);
157
158		#[block]
159		{
160			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
161		}
162
163		assert!(<DeletionQueue<T>>::iter().next().is_none(), "deletion queue should be drained",);
164		Ok(())
165	}
166
167	/// Measures the cost of clearing one [`NativeDepositOf`] row during
168	/// [`ContractInfo::process_deletion_queue_batch`]. Pre-populates the contract with `k`
169	/// per-payer rows and queues the contract for deletion with `native_cleared = false` and
170	/// an empty trie. The deletion queue then drains all rows in one go.
171	#[benchmark(skip_meta, pov_mode = Measured)]
172	fn deletion_queue_per_native_deposit_key(k: Linear<0, 1024>) -> Result<(), BenchmarkError> {
173		use frame_benchmarking::v2::account;
174
175		// Empty trie: zero items, zero bytes; we only want to measure native-deposit cleanup.
176		let instance = Contract::<T>::with_storage(VmBinaryModule::dummy(), 0, 0)?;
177		for i in 0..k {
178			let payer: T::AccountId = account("payer", i, 0);
179			NativeDepositOf::<T>::insert(&instance.account_id, &payer, BalanceOf::<T>::default());
180		}
181		ContractInfo::<T>::queue_for_deletion(
182			instance.info()?.trie_id,
183			instance.account_id.clone(),
184		);
185
186		#[block]
187		{
188			ContractInfo::<T>::process_deletion_queue_batch(&mut WeightMeter::new())
189		}
190
191		assert!(<DeletionQueue<T>>::iter().next().is_none(), "deletion queue should be drained",);
192		Ok(())
193	}
194
195	// This benchmarks the overhead of loading a code of size `c` byte from storage and into
196	// the execution engine.
197	//
198	// `call_with_pvm_code_per_byte(c) - call_with_pvm_code_per_byte(0)`
199	//
200	// This does **not** include the actual execution for which the gas meter
201	// is responsible. The code used here will just return on call.
202	//
203	// We expect the influence of `c` to be none in this benchmark because every instruction that
204	// is not in the first basic block is never read. We are primarily interested in the
205	// `proof_size` result of this benchmark.
206	#[benchmark(pov_mode = Measured)]
207	fn call_with_pvm_code_per_byte(c: Linear<0, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
208		let instance =
209			Contract::<T>::with_caller(whitelisted_caller(), VmBinaryModule::sized(c), vec![])?;
210		let value = Pallet::<T>::min_balance();
211		let storage_deposit = default_deposit_limit::<T>();
212
213		#[extrinsic_call]
214		call(
215			RawOrigin::Signed(instance.caller.clone()),
216			instance.address,
217			value,
218			Weight::MAX,
219			storage_deposit,
220			vec![],
221		);
222
223		Ok(())
224	}
225
226	// This benchmarks the overhead of loading a code of size `c` byte from storage and into
227	// the execution engine.
228	/// This is similar to `call_with_pvm_code_per_byte` but for EVM bytecode.
229	#[benchmark(pov_mode = Measured)]
230	fn call_with_evm_code_per_byte(c: Linear<1, { 10 * 1024 }>) -> Result<(), BenchmarkError> {
231		let instance = Contract::<T>::with_caller(
232			whitelisted_caller(),
233			VmBinaryModule::evm_init_code_for_runtime_size(c),
234			vec![],
235		)?;
236		let value = Pallet::<T>::min_balance();
237		let storage_deposit = default_deposit_limit::<T>();
238
239		let code_len = PristineCode::<T>::get(instance.info()?.code_hash)
240			.expect("code should be stored")
241			.len();
242		assert_eq!(
243			code_len, c as usize,
244			"runtime bytecode should be exactly {c} bytes, got {code_len}"
245		);
246
247		#[extrinsic_call]
248		call(
249			RawOrigin::Signed(instance.caller.clone()),
250			instance.address,
251			value,
252			Weight::MAX,
253			storage_deposit,
254			vec![],
255		);
256
257		Ok(())
258	}
259
260	// Measure the amount of time it takes to compile a single basic block.
261	//
262	// (basic_block_compilation(1) - basic_block_compilation(0)).ref_time()
263	//
264	// This is needed because the interpreter will always compile a whole basic block at
265	// a time. To prevent a contract from triggering compilation without doing any execution
266	// we will always charge one max sized block per contract call.
267	//
268	// We ignore the proof size component when using this benchmark as this is already accounted
269	// for in `call_with_pvm_code_per_byte`.
270	#[benchmark(pov_mode = Measured)]
271	fn basic_block_compilation(b: Linear<0, 1>) -> Result<(), BenchmarkError> {
272		let instance = Contract::<T>::with_caller(
273			whitelisted_caller(),
274			VmBinaryModule::with_num_instructions(limits::code::BASIC_BLOCK_SIZE),
275			vec![],
276		)?;
277		let value = Pallet::<T>::min_balance();
278		let storage_deposit = default_deposit_limit::<T>();
279
280		#[block]
281		{
282			Pallet::<T>::call(
283				RawOrigin::Signed(instance.caller.clone()).into(),
284				instance.address,
285				value,
286				Weight::MAX,
287				storage_deposit,
288				vec![],
289			)?;
290		}
291
292		Ok(())
293	}
294
295	// `c`: Size of the code in bytes.
296	// `i`: Size of the input in bytes.
297	#[benchmark(pov_mode = Measured)]
298	fn instantiate_with_code(
299		c: Linear<0, { 100 * 1024 }>,
300		i: Linear<0, { limits::CALLDATA_BYTES }>,
301	) {
302		let pallet_account = whitelisted_pallet_account::<T>();
303		let input = vec![42u8; i as usize];
304		let salt = [42u8; 32];
305		let value = Pallet::<T>::min_balance();
306		let caller = whitelisted_caller();
307		T::Currency::set_balance(&caller, caller_funding::<T>());
308		let VmBinaryModule { code, .. } = VmBinaryModule::sized(c);
309		let origin = RawOrigin::Signed(caller.clone());
310		if !T::AddressMapper::is_mapped(&caller) {
311			T::AddressMapper::map(&caller).unwrap();
312		}
313		let deployer = T::AddressMapper::to_address(&caller);
314		let addr = crate::address::create2(&deployer, &code, &input, &salt);
315		let account_id = T::AddressMapper::to_fallback_account_id(&addr);
316		let storage_deposit = default_deposit_limit::<T>();
317		#[extrinsic_call]
318		_(origin, value, Weight::MAX, storage_deposit, code, input, Some(salt));
319
320		let deposit =
321			T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id);
322		// uploading the code reserves some balance in the pallet's account
323		let code_deposit = T::Currency::balance_on_hold(
324			&HoldReason::CodeUploadDepositReserve.into(),
325			&pallet_account,
326		);
327		let mapping_deposit =
328			T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &caller);
329		assert_eq!(
330			T::Currency::balance(&caller),
331			caller_funding::<T>() - value - deposit - code_deposit - mapping_deposit,
332		);
333		// contract has the full value
334		assert_eq!(T::Currency::balance(&account_id), value + Pallet::<T>::min_balance());
335	}
336
337	// `c`: Size of the code in bytes.
338	// `i`: Size of the input in bytes.
339	// `d`: with or without dust value to transfer
340	#[benchmark(pov_mode = Measured)]
341	fn eth_instantiate_with_code(
342		c: Linear<0, { 100 * 1024 }>,
343		i: Linear<0, { limits::CALLDATA_BYTES }>,
344		d: Linear<0, 1>,
345	) -> Result<(), BenchmarkError> {
346		let input = vec![42u8; i as usize];
347
348		// Use an `effective_gas_price` that is not a multiple of `T::NativeToEthRatio`
349		// to hit the code that charge the rounding error so that tx_cost == effective_gas_price *
350		// gas_used
351		let effective_gas_price = Pallet::<T>::evm_base_fee() + 1;
352		let value = Pallet::<T>::min_balance();
353		let dust = 42u32 * d;
354		let evm_value =
355			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
356		let caller = whitelisted_caller();
357		T::Currency::set_balance(&caller, caller_funding::<T>());
358		let VmBinaryModule { code, .. } = VmBinaryModule::sized(c);
359		let origin = Origin::EthTransaction(caller.clone());
360		if !T::AddressMapper::is_mapped(&caller) {
361			T::AddressMapper::map(&caller).unwrap();
362		}
363		let deployer = T::AddressMapper::to_address(&caller);
364		let nonce = System::<T>::account_nonce(&caller).try_into().unwrap_or_default();
365		let addr = crate::address::create1(&deployer, nonce);
366
367		assert!(AccountInfoOf::<T>::get(&deployer).is_none());
368
369		<T as Config>::FeeInfo::deposit_txfee(
370			<T as Config>::Currency::issue(caller_funding::<T>()),
371		);
372
373		#[extrinsic_call]
374		_(
375			origin,
376			evm_value,
377			Weight::MAX,
378			U256::MAX,
379			code,
380			input,
381			TransactionSigned::default().signed_payload(),
382			effective_gas_price,
383			0,
384		);
385
386		// contract has the full value
387		assert_eq!(Pallet::<T>::evm_balance(&addr), evm_value);
388		Ok(())
389	}
390
391	#[benchmark(pov_mode = Measured)]
392	fn deposit_eth_extrinsic_revert_event() {
393		#[block]
394		{
395			Pallet::<T>::deposit_event(Event::<T>::EthExtrinsicRevert {
396				dispatch_error: crate::Error::<T>::BenchmarkingError.into(),
397			});
398		}
399	}
400
401	// `i`: Size of the input in bytes.
402	// `s`: Size of e salt in bytes.
403	#[benchmark(pov_mode = Measured)]
404	fn instantiate(i: Linear<0, { limits::CALLDATA_BYTES }>) -> Result<(), BenchmarkError> {
405		let pallet_account = whitelisted_pallet_account::<T>();
406		let input = vec![42u8; i as usize];
407		let salt = [42u8; 32];
408		let value = Pallet::<T>::min_balance();
409		let caller = whitelisted_caller();
410		T::Currency::set_balance(&caller, caller_funding::<T>());
411		let origin = RawOrigin::Signed(caller.clone());
412		if !T::AddressMapper::is_mapped(&caller) {
413			T::AddressMapper::map(&caller).unwrap();
414		}
415		let VmBinaryModule { code, .. } = VmBinaryModule::dummy();
416		let storage_deposit = default_deposit_limit::<T>();
417		let deployer = T::AddressMapper::to_address(&caller);
418		let addr = crate::address::create2(&deployer, &code, &input, &salt);
419		let hash = Contracts::<T>::bare_upload_code(origin.clone().into(), code, storage_deposit)?
420			.code_hash;
421		let account_id = T::AddressMapper::to_fallback_account_id(&addr);
422
423		#[extrinsic_call]
424		_(origin, value, Weight::MAX, storage_deposit, hash, input, Some(salt));
425
426		let deposit =
427			T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id);
428		let code_deposit = T::Currency::balance_on_hold(
429			&HoldReason::CodeUploadDepositReserve.into(),
430			&pallet_account,
431		);
432		let mapping_deposit =
433			T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &account_id);
434		// value was removed from the caller
435		assert_eq!(
436			T::Currency::total_balance(&caller),
437			caller_funding::<T>() - value - deposit - code_deposit - mapping_deposit,
438		);
439		// contract has the full value
440		assert_eq!(T::Currency::balance(&account_id), value + Pallet::<T>::min_balance());
441
442		Ok(())
443	}
444
445	// We just call a dummy contract to measure the overhead of the call extrinsic.
446	// The size of the data has no influence on the costs of this extrinsic as long as the contract
447	// won't call `seal_call_data_copy` in its constructor to copy the data to contract memory.
448	// The dummy contract used here does not do this. The costs for the data copy is billed as
449	// part of `seal_call_data_copy`. The costs for invoking a contract of a specific size are not
450	// part of this benchmark because we cannot know the size of the contract when issuing a call
451	// transaction. See `call_with_pvm_code_per_byte` for this.
452	#[benchmark(pov_mode = Measured)]
453	fn call() -> Result<(), BenchmarkError> {
454		let pallet_account = whitelisted_pallet_account::<T>();
455		let data = vec![42u8; 1024];
456		let instance =
457			Contract::<T>::with_caller(whitelisted_caller(), VmBinaryModule::dummy(), vec![])?;
458		let value = Pallet::<T>::min_balance();
459		let origin = RawOrigin::Signed(instance.caller.clone());
460		let before = T::Currency::balance(&instance.account_id);
461		let storage_deposit = default_deposit_limit::<T>();
462		#[extrinsic_call]
463		_(origin, instance.address, value, Weight::MAX, storage_deposit, data);
464		let deposit = T::Currency::balance_on_hold(
465			&HoldReason::StorageDepositReserve.into(),
466			&instance.account_id,
467		);
468		let code_deposit = T::Currency::balance_on_hold(
469			&HoldReason::CodeUploadDepositReserve.into(),
470			&pallet_account,
471		);
472		let mapping_deposit =
473			T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &instance.caller);
474		// value and value transferred via call should be removed from the caller
475		assert_eq!(
476			T::Currency::balance(&instance.caller),
477			caller_funding::<T>() - value - deposit - code_deposit - mapping_deposit,
478		);
479		// contract should have received the value
480		assert_eq!(T::Currency::balance(&instance.account_id), before + value);
481		// contract should still exist
482		instance.info()?;
483
484		Ok(())
485	}
486
487	// `d`: with or without dust value to transfer
488	#[benchmark(pov_mode = Measured)]
489	fn eth_call(d: Linear<0, 1>) -> Result<(), BenchmarkError> {
490		let data = vec![42u8; 1024];
491		let instance =
492			Contract::<T>::with_caller(whitelisted_caller(), VmBinaryModule::dummy(), vec![])?;
493
494		// Use an `effective_gas_price` that is not a multiple of `T::NativeToEthRatio`
495		// to hit the code that charge the rounding error so that tx_cost == effective_gas_price *
496		// gas_used
497		let effective_gas_price = Pallet::<T>::evm_base_fee() + 1;
498		let value = Pallet::<T>::min_balance();
499		let dust = 42u32 * d;
500		let evm_value =
501			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
502
503		// need to pass the overdraw check
504		<T as Config>::FeeInfo::deposit_txfee(
505			<T as Config>::Currency::issue(caller_funding::<T>()),
506		);
507
508		let origin = Origin::EthTransaction(instance.caller.clone());
509		let before = Pallet::<T>::evm_balance(&instance.address);
510
511		#[extrinsic_call]
512		_(
513			origin,
514			instance.address,
515			evm_value,
516			Weight::MAX,
517			U256::MAX,
518			data,
519			TransactionSigned::default().signed_payload(),
520			effective_gas_price,
521			0,
522		);
523
524		// contract should have received the value
525		assert_eq!(Pallet::<T>::evm_balance(&instance.address), before + evm_value);
526		// contract should still exist
527		instance.info()?;
528
529		Ok(())
530	}
531
532	// `c`: Size of the RLP encoded Ethereum transaction in bytes.
533	#[benchmark(pov_mode = Measured)]
534	fn eth_substrate_call(c: Linear<0, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
535		let caller = whitelisted_caller();
536		T::Currency::set_balance(&caller, caller_funding::<T>());
537		let origin = Origin::EthTransaction(caller);
538		let dispatchable = frame_system::Call::remark { remark: vec![] }.into();
539		#[extrinsic_call]
540		_(origin, Box::new(dispatchable), vec![42u8; c as usize]);
541		Ok(())
542	}
543
544	// This constructs a contract that is maximal expensive to instrument.
545	// It creates a maximum number of metering blocks per byte.
546	// `c`: Size of the code in bytes.
547	#[benchmark(pov_mode = Measured)]
548	fn upload_code(c: Linear<0, { 100 * 1024 }>) {
549		let caller = whitelisted_caller();
550		let pallet_account = whitelisted_pallet_account::<T>();
551		T::Currency::set_balance(&caller, caller_funding::<T>());
552		let VmBinaryModule { code, hash, .. } = VmBinaryModule::sized(c);
553		let origin = RawOrigin::Signed(caller.clone());
554		let storage_deposit = default_deposit_limit::<T>();
555		#[extrinsic_call]
556		_(origin, code, storage_deposit);
557		// uploading the code reserves some balance in the pallet's account
558		assert!(T::Currency::total_balance_on_hold(&pallet_account) > 0u32.into());
559		assert!(<Contract<T>>::code_exists(&hash));
560	}
561
562	// Removing code does not depend on the size of the contract because all the information
563	// needed to verify the removal claim (refcount, owner) is stored in a separate storage
564	// item (`CodeInfoOf`).
565	#[benchmark(pov_mode = Measured)]
566	fn remove_code() -> Result<(), BenchmarkError> {
567		let caller = whitelisted_caller();
568		let pallet_account = whitelisted_pallet_account::<T>();
569		T::Currency::set_balance(&caller, caller_funding::<T>());
570		let VmBinaryModule { code, hash, .. } = VmBinaryModule::dummy();
571		let origin = RawOrigin::Signed(caller.clone());
572		let storage_deposit = default_deposit_limit::<T>();
573		let uploaded =
574			<Contracts<T>>::bare_upload_code(origin.clone().into(), code, storage_deposit)?;
575		assert_eq!(uploaded.code_hash, hash);
576		assert_eq!(uploaded.deposit, T::Currency::total_balance_on_hold(&pallet_account));
577		assert!(<Contract<T>>::code_exists(&hash));
578		#[extrinsic_call]
579		_(origin, hash);
580		// removing the code should have unreserved the deposit
581		assert_eq!(T::Currency::total_balance_on_hold(&pallet_account), 0u32.into());
582		assert!(<Contract<T>>::code_removed(&hash));
583		Ok(())
584	}
585
586	#[benchmark(pov_mode = Measured)]
587	fn set_code() -> Result<(), BenchmarkError> {
588		let instance =
589			<Contract<T>>::with_caller(whitelisted_caller(), VmBinaryModule::dummy(), vec![])?;
590		// we just add some bytes so that the code hash is different
591		let VmBinaryModule { code, .. } = VmBinaryModule::dummy_unique(128);
592		let origin = RawOrigin::Signed(instance.caller.clone());
593		let storage_deposit = default_deposit_limit::<T>();
594		let hash =
595			<Contracts<T>>::bare_upload_code(origin.into(), code, storage_deposit)?.code_hash;
596		assert_ne!(instance.info()?.code_hash, hash);
597		#[extrinsic_call]
598		_(RawOrigin::Root, instance.address, hash);
599		assert_eq!(instance.info()?.code_hash, hash);
600		Ok(())
601	}
602
603	#[benchmark(pov_mode = Measured)]
604	fn map_account() {
605		let caller = whitelisted_caller();
606		T::Currency::set_balance(&caller, caller_funding::<T>());
607		let origin = RawOrigin::Signed(caller.clone());
608		if T::AddressMapper::is_mapped(&caller) {
609			T::AddressMapper::unmap(&caller).unwrap();
610		}
611		assert!(!T::AddressMapper::is_mapped(&caller));
612		#[extrinsic_call]
613		_(origin);
614		assert!(T::AddressMapper::is_mapped(&caller));
615	}
616
617	#[benchmark(pov_mode = Measured)]
618	fn unmap_account() {
619		let caller = whitelisted_caller();
620		T::Currency::set_balance(&caller, caller_funding::<T>());
621		let origin = RawOrigin::Signed(caller.clone());
622		if !T::AddressMapper::is_mapped(&caller) {
623			T::AddressMapper::map(&caller).unwrap();
624		}
625		assert!(T::AddressMapper::is_mapped(&caller));
626		#[extrinsic_call]
627		_(origin);
628		assert!(!T::AddressMapper::is_mapped(&caller));
629	}
630
631	/// Worst case: every input account is not eth-derived, not yet mapped, and
632	/// already carries an [`HoldReason::AddressMapping`] hold. The per-account
633	/// loop body in `batch_map_accounts` then both inserts the [`OriginalAccount`]
634	/// entry via `map_no_deposit_unchecked` *and* releases the existing hold.
635	#[benchmark(pov_mode = Measured)]
636	fn batch_map_accounts(a: Linear<0, 1024>) -> Result<(), BenchmarkError> {
637		use frame_benchmarking::v2::account;
638
639		let caller: T::AccountId = whitelisted_caller();
640		T::Currency::set_balance(&caller, caller_funding::<T>());
641
642		// Matches the deposit that `AccountId32Mapper::map` would normally take.
643		let deposit = T::DepositPerByte::get()
644			.saturating_mul(52u32.into())
645			.saturating_add(T::DepositPerItem::get());
646
647		let mut accounts = Vec::with_capacity(a as usize);
648		for i in 0..a {
649			let account_id: T::AccountId = account("to_map", i, 0);
650			T::Currency::set_balance(&account_id, caller_funding::<T>());
651			T::Currency::hold(&HoldReason::AddressMapping.into(), &account_id, deposit)?;
652			accounts.push(account_id);
653		}
654
655		#[extrinsic_call]
656		_(RawOrigin::Signed(caller), accounts.clone());
657
658		for account_id in &accounts {
659			assert!(T::AddressMapper::is_mapped(account_id));
660			assert_eq!(
661				T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), account_id),
662				0u32.into(),
663			);
664		}
665
666		Ok(())
667	}
668
669	#[benchmark(pov_mode = Measured)]
670	fn dispatch_as_fallback_account() {
671		let caller = whitelisted_caller();
672		T::Currency::set_balance(&caller, caller_funding::<T>());
673		let origin = RawOrigin::Signed(caller.clone());
674		let dispatchable = frame_system::Call::remark { remark: vec![] }.into();
675		#[extrinsic_call]
676		_(origin, Box::new(dispatchable));
677	}
678
679	#[benchmark(pov_mode = Measured)]
680	fn noop_host_fn(r: Linear<0, API_BENCHMARK_RUNS>) {
681		let mut setup = CallSetup::<T>::new(VmBinaryModule::noop());
682		let (mut ext, module) = setup.ext();
683		let prepared = CallSetup::<T>::prepare_call(&mut ext, module, r.encode(), 0);
684		#[block]
685		{
686			prepared.call().unwrap();
687		}
688	}
689
690	#[benchmark(pov_mode = Measured)]
691	fn seal_caller() {
692		let len = H160::len_bytes();
693		build_runtime!(runtime, memory: [vec![0u8; len as _], ]);
694
695		let result;
696		#[block]
697		{
698			result = runtime.bench_caller(memory.as_mut_slice(), 0);
699		}
700
701		assert_ok!(result);
702		assert_eq!(
703			<H160 as Decode>::decode(&mut &memory[..]).unwrap(),
704			T::AddressMapper::to_address(&runtime.ext().caller().account_id().unwrap())
705		);
706	}
707
708	#[benchmark(pov_mode = Measured)]
709	fn seal_origin() {
710		let len = H160::len_bytes();
711		build_runtime!(runtime, memory: [vec![0u8; len as _], ]);
712
713		let result;
714		#[block]
715		{
716			result = runtime.bench_origin(memory.as_mut_slice(), 0);
717		}
718
719		assert_ok!(result);
720		assert_eq!(
721			<H160 as Decode>::decode(&mut &memory[..]).unwrap(),
722			T::AddressMapper::to_address(&runtime.ext().origin().account_id().unwrap())
723		);
724	}
725
726	#[benchmark(pov_mode = Measured)]
727	fn to_account_id() {
728		// use a mapped address for the benchmark, to ensure that we bench the worst
729		// case (and not the fallback case).
730		let account_id = account("precompile_to_account_id", 0, 0);
731		let address = {
732			T::Currency::set_balance(&account_id, caller_funding::<T>());
733			if !T::AddressMapper::is_mapped(&account_id) {
734				T::AddressMapper::map(&account_id).unwrap();
735			}
736			T::AddressMapper::to_address(&account_id)
737		};
738
739		let input_bytes = ISystem::ISystemCalls::toAccountId(ISystem::toAccountIdCall {
740			input: address.0.into(),
741		})
742		.abi_encode();
743
744		let mut call_setup = CallSetup::<T>::default();
745		let (mut ext, _) = call_setup.ext();
746
747		let result;
748		#[block]
749		{
750			result = run_builtin_precompile(
751				&mut ext,
752				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
753				input_bytes,
754			);
755		}
756		let raw_data = result.unwrap().data;
757		let data = Bytes::abi_decode(&raw_data).expect("decoding failed");
758		assert_ne!(
759			data.0.as_ref()[20..32],
760			[0xEE; 12],
761			"fallback suffix found where none should be"
762		);
763		assert_eq!(T::AccountId::decode(&mut data.as_ref()), Ok(account_id),);
764	}
765
766	#[benchmark(pov_mode = Measured)]
767	fn seal_code_hash() {
768		let contract = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
769		let len = <sp_core::H256 as MaxEncodedLen>::max_encoded_len() as u32;
770		build_runtime!(runtime, memory: [vec![0u8; len as _], contract.account_id.encode(), ]);
771
772		let result;
773		#[block]
774		{
775			result = runtime.bench_code_hash(memory.as_mut_slice(), len, 0);
776		}
777
778		assert_ok!(result);
779		assert_eq!(
780			<sp_core::H256 as Decode>::decode(&mut &memory[..]).unwrap(),
781			contract.info().unwrap().code_hash
782		);
783	}
784
785	#[benchmark(pov_mode = Measured)]
786	fn own_code_hash() {
787		let input_bytes =
788			ISystem::ISystemCalls::ownCodeHash(ISystem::ownCodeHashCall {}).abi_encode();
789		let mut call_setup = CallSetup::<T>::default();
790		let contract_acc = call_setup.contract().account_id.clone();
791		let caller = call_setup.contract().address;
792		call_setup.set_origin(ExecOrigin::from_account_id(contract_acc));
793		let (mut ext, _) = call_setup.ext();
794
795		let result;
796		#[block]
797		{
798			result = run_builtin_precompile(
799				&mut ext,
800				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
801				input_bytes,
802			);
803		}
804		assert!(result.is_ok());
805		let caller_code_hash = ext.code_hash(&caller);
806		assert_eq!(caller_code_hash.0.to_vec(), result.unwrap().data);
807	}
808
809	#[benchmark(pov_mode = Measured)]
810	fn seal_code_size() {
811		let contract = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
812		build_runtime!(runtime, memory: [contract.address.encode(),]);
813
814		let result;
815		#[block]
816		{
817			result = runtime.bench_code_size(memory.as_mut_slice(), 0);
818		}
819
820		assert_eq!(result.unwrap(), VmBinaryModule::dummy().code.len() as u64);
821	}
822
823	#[benchmark(pov_mode = Measured)]
824	fn caller_is_origin() {
825		let input_bytes =
826			ISystem::ISystemCalls::callerIsOrigin(ISystem::callerIsOriginCall {}).abi_encode();
827
828		let mut call_setup = CallSetup::<T>::default();
829		let (mut ext, _) = call_setup.ext();
830
831		let result;
832		#[block]
833		{
834			result = run_builtin_precompile(
835				&mut ext,
836				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
837				input_bytes,
838			);
839		}
840		let raw_data = result.unwrap().data;
841		let is_origin = Bool::abi_decode(&raw_data[..]).expect("decoding failed");
842		assert!(is_origin);
843	}
844
845	#[benchmark(pov_mode = Measured)]
846	fn caller_is_root() {
847		let input_bytes =
848			ISystem::ISystemCalls::callerIsRoot(ISystem::callerIsRootCall {}).abi_encode();
849
850		let mut setup = CallSetup::<T>::default();
851		setup.set_origin(ExecOrigin::Root);
852		let (mut ext, _) = setup.ext();
853
854		let result;
855		#[block]
856		{
857			result = run_builtin_precompile(
858				&mut ext,
859				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
860				input_bytes,
861			);
862		}
863		let raw_data = result.unwrap().data;
864		let is_root = Bool::abi_decode(&raw_data).expect("decoding failed");
865		assert!(is_root);
866	}
867
868	#[benchmark(pov_mode = Measured)]
869	fn seal_address() {
870		let len = H160::len_bytes();
871		build_runtime!(runtime, memory: [vec![0u8; len as _], ]);
872
873		let result;
874		#[block]
875		{
876			result = runtime.bench_address(memory.as_mut_slice(), 0);
877		}
878		assert_ok!(result);
879		assert_eq!(<H160 as Decode>::decode(&mut &memory[..]).unwrap(), runtime.ext().address());
880	}
881
882	#[benchmark(pov_mode = Measured)]
883	fn weight_left() {
884		let input_bytes =
885			ISystem::ISystemCalls::weightLeft(ISystem::weightLeftCall {}).abi_encode();
886
887		let mut call_setup = CallSetup::<T>::default();
888		let (mut ext, _) = call_setup.ext();
889
890		let weight_left_before = ext.frame_meter().weight_left().unwrap();
891		let result;
892		#[block]
893		{
894			result = run_builtin_precompile(
895				&mut ext,
896				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
897				input_bytes,
898			);
899		}
900		let weight_left_after = ext.frame_meter().weight_left().unwrap();
901		assert_ne!(weight_left_after.ref_time(), 0);
902		assert!(weight_left_before.ref_time() > weight_left_after.ref_time());
903
904		let raw_data = result.unwrap().data;
905		type MyTy = (Uint<64>, Uint<64>);
906		let foo = MyTy::abi_decode(&raw_data[..]).unwrap();
907		assert_eq!(weight_left_after.ref_time(), foo.0);
908	}
909
910	#[benchmark(pov_mode = Measured)]
911	fn seal_ref_time_left() {
912		build_runtime!(runtime, memory: [vec![], ]);
913
914		let result;
915		#[block]
916		{
917			result = runtime.bench_ref_time_left(memory.as_mut_slice());
918		}
919		assert_eq!(result.unwrap(), runtime.ext().gas_left());
920	}
921
922	#[benchmark(pov_mode = Measured)]
923	fn seal_balance() {
924		build_runtime!(runtime, contract, memory: [[0u8;32], ]);
925		contract.set_balance(BalanceWithDust::new_unchecked::<T>(
926			Pallet::<T>::min_balance() * 2u32.into(),
927			42u32,
928		));
929
930		let result;
931		#[block]
932		{
933			result = runtime.bench_balance(memory.as_mut_slice(), 0);
934		}
935		assert_ok!(result);
936		assert_eq!(
937			U256::from_little_endian(&memory[..]),
938			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(
939				Pallet::<T>::min_balance(),
940				42
941			))
942		);
943	}
944
945	#[benchmark(pov_mode = Measured)]
946	fn seal_balance_of() {
947		let len = <sp_core::U256 as MaxEncodedLen>::max_encoded_len();
948		let account = account::<T::AccountId>("target", 0, 0);
949		<T as Config>::AddressMapper::map_no_deposit_unchecked(&account).unwrap();
950
951		let address = T::AddressMapper::to_address(&account);
952		let balance = Pallet::<T>::min_balance() * 2u32.into();
953		T::Currency::set_balance(&account, balance);
954		AccountInfoOf::<T>::insert(&address, AccountInfo { dust: 42, ..Default::default() });
955
956		build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]);
957
958		let result;
959		#[block]
960		{
961			result = runtime.bench_balance_of(memory.as_mut_slice(), len as u32, 0);
962		}
963
964		assert_ok!(result);
965		assert_eq!(
966			U256::from_little_endian(&memory[..len]),
967			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(
968				Pallet::<T>::min_balance(),
969				42
970			))
971		);
972	}
973
974	#[benchmark(pov_mode = Measured)]
975	fn seal_get_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) {
976		let len = n as usize;
977		let immutable_data = vec![1u8; len];
978
979		build_runtime!(runtime, contract, memory: [(len as u32).encode(), vec![0u8; len],]);
980
981		<ImmutableDataOf<T>>::insert::<_, BoundedVec<_, _>>(
982			contract.address,
983			immutable_data.clone().try_into().unwrap(),
984		);
985
986		let result;
987		#[block]
988		{
989			result = runtime.bench_get_immutable_data(memory.as_mut_slice(), 4, 0 as u32);
990		}
991
992		assert_ok!(result);
993		assert_eq!(&memory[0..4], (len as u32).encode());
994		assert_eq!(&memory[4..len + 4], &immutable_data);
995	}
996
997	#[benchmark(pov_mode = Measured)]
998	fn seal_set_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) {
999		let len = n as usize;
1000		let mut memory = vec![1u8; len];
1001		let mut setup = CallSetup::<T>::default();
1002		let input = setup.data();
1003		let (mut ext, _) = setup.ext();
1004		ext.override_export(crate::exec::ExportedFunction::Constructor);
1005
1006		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input);
1007
1008		let result;
1009		#[block]
1010		{
1011			result = runtime.bench_set_immutable_data(memory.as_mut_slice(), 0, n);
1012		}
1013
1014		assert_ok!(result);
1015		assert_eq!(&memory[..], &<ImmutableDataOf<T>>::get(setup.contract().address).unwrap()[..]);
1016	}
1017
1018	#[benchmark(pov_mode = Measured)]
1019	fn seal_value_transferred() {
1020		build_runtime!(runtime, memory: [[0u8;32], ]);
1021		let result;
1022		#[block]
1023		{
1024			result = runtime.bench_value_transferred(memory.as_mut_slice(), 0);
1025		}
1026		assert_ok!(result);
1027		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().value_transferred());
1028	}
1029
1030	#[benchmark(pov_mode = Measured)]
1031	fn minimum_balance() {
1032		let input_bytes =
1033			ISystem::ISystemCalls::minimumBalance(ISystem::minimumBalanceCall {}).abi_encode();
1034
1035		let mut call_setup = CallSetup::<T>::default();
1036		let (mut ext, _) = call_setup.ext();
1037
1038		let result;
1039		#[block]
1040		{
1041			result = run_builtin_precompile(
1042				&mut ext,
1043				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1044				input_bytes,
1045			);
1046		}
1047		let min: U256 = crate::Pallet::<T>::convert_native_to_evm(T::Currency::minimum_balance());
1048		let min =
1049			crate::precompiles::alloy::primitives::aliases::U256::abi_decode(&min.to_big_endian())
1050				.unwrap();
1051
1052		let raw_data = result.unwrap().data;
1053		let returned_min =
1054			crate::precompiles::alloy::primitives::aliases::U256::abi_decode(&raw_data)
1055				.expect("decoding failed");
1056		assert_eq!(returned_min, min);
1057	}
1058
1059	#[benchmark(pov_mode = Measured)]
1060	fn seal_return_data_size() {
1061		let mut setup = CallSetup::<T>::default();
1062		let (mut ext, _) = setup.ext();
1063		let mut runtime = pvm::Runtime::new(&mut ext, vec![]);
1064		let mut memory = memory!(vec![],);
1065		*runtime.ext().last_frame_output_mut() =
1066			ExecReturnValue { data: vec![42; 256], ..Default::default() };
1067		let result;
1068		#[block]
1069		{
1070			result = runtime.bench_return_data_size(memory.as_mut_slice());
1071		}
1072		assert_eq!(result.unwrap(), 256);
1073	}
1074
1075	#[benchmark(pov_mode = Measured)]
1076	fn seal_call_data_size() {
1077		let mut setup = CallSetup::<T>::default();
1078		let (mut ext, _) = setup.ext();
1079		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 128 as usize]);
1080		let mut memory = memory!(vec![0u8; 4],);
1081		let result;
1082		#[block]
1083		{
1084			result = runtime.bench_call_data_size(memory.as_mut_slice());
1085		}
1086		assert_eq!(result.unwrap(), 128);
1087	}
1088
1089	#[benchmark(pov_mode = Measured)]
1090	fn seal_gas_limit() {
1091		build_runtime!(runtime, memory: []);
1092		let result;
1093		#[block]
1094		{
1095			result = runtime.bench_gas_limit(&mut memory);
1096		}
1097		assert_eq!(U256::from(result.unwrap()), <Pallet<T>>::evm_block_gas_limit());
1098	}
1099
1100	#[benchmark(pov_mode = Measured)]
1101	fn seal_gas_price() {
1102		build_runtime!(runtime, memory: []);
1103		let result;
1104		#[block]
1105		{
1106			result = runtime.bench_gas_price(memory.as_mut_slice());
1107		}
1108		assert_eq!(U256::from(result.unwrap()), <Pallet<T>>::evm_base_fee());
1109	}
1110
1111	#[benchmark(pov_mode = Measured)]
1112	fn seal_base_fee() {
1113		build_runtime!(runtime, memory: [[1u8;32], ]);
1114		let result;
1115		#[block]
1116		{
1117			result = runtime.bench_base_fee(memory.as_mut_slice(), 0);
1118		}
1119		assert_ok!(result);
1120		assert_eq!(U256::from_little_endian(&memory[..]), <crate::Pallet<T>>::evm_base_fee());
1121	}
1122
1123	#[benchmark(pov_mode = Measured)]
1124	fn seal_block_number() {
1125		build_runtime!(runtime, memory: [[0u8;32], ]);
1126		let result;
1127		#[block]
1128		{
1129			result = runtime.bench_block_number(memory.as_mut_slice(), 0);
1130		}
1131		assert_ok!(result);
1132		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().block_number());
1133	}
1134
1135	#[benchmark(pov_mode = Measured)]
1136	fn seal_block_author() {
1137		build_runtime!(runtime, memory: [[123u8; 20], ]);
1138
1139		// The pre-runtime digest log is unbounded; usually around 3 items but it can vary.
1140		// To get safe benchmark results despite that, populate it with a bunch of random logs to
1141		// ensure iteration over many items (we just overestimate the cost of the API).
1142		for i in 0..16 {
1143			frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1144				[i, i, i, i],
1145				vec![i; 128],
1146			));
1147			frame_system::Pallet::<T>::deposit_log(DigestItem::Consensus(
1148				[i, i, i, i],
1149				vec![i; 128],
1150			));
1151			frame_system::Pallet::<T>::deposit_log(DigestItem::Seal([i, i, i, i], vec![i; 128]));
1152			frame_system::Pallet::<T>::deposit_log(DigestItem::Other(vec![i; 128]));
1153		}
1154
1155		// The content of the pre-runtime digest log depends on the configured consensus.
1156		// However, mismatching logs are simply ignored. Thus we construct fixtures which will
1157		// let the API to return a value in both BABE and AURA consensus.
1158
1159		// Construct a `Digest` log fixture returning some value in BABE
1160		let primary_pre_digest = vec![0; <PrimaryPreDigest as MaxEncodedLen>::max_encoded_len()];
1161		let pre_digest =
1162			PreDigest::Primary(PrimaryPreDigest::decode(&mut &primary_pre_digest[..]).unwrap());
1163		frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1164			BABE_ENGINE_ID,
1165			pre_digest.encode(),
1166		));
1167		frame_system::Pallet::<T>::deposit_log(DigestItem::Seal(
1168			BABE_ENGINE_ID,
1169			pre_digest.encode(),
1170		));
1171
1172		// Construct a `Digest` log fixture returning some value in AURA
1173		let slot = Slot::default();
1174		frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1175			AURA_ENGINE_ID,
1176			slot.encode(),
1177		));
1178		frame_system::Pallet::<T>::deposit_log(DigestItem::Seal(AURA_ENGINE_ID, slot.encode()));
1179
1180		let result;
1181		#[block]
1182		{
1183			result = runtime.bench_block_author(memory.as_mut_slice(), 0);
1184		}
1185		assert_ok!(result);
1186
1187		let block_author = runtime.ext().block_author();
1188		assert_eq!(&memory[..], block_author.as_bytes());
1189	}
1190
1191	#[benchmark(pov_mode = Measured)]
1192	fn seal_block_hash() {
1193		let mut memory = vec![0u8; 64];
1194		let mut setup = CallSetup::<T>::default();
1195		let input = setup.data();
1196		let (mut ext, _) = setup.ext();
1197		ext.set_block_number(BlockNumberFor::<T>::from(1u32));
1198
1199		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input);
1200
1201		let block_hash = H256::from([1; 32]);
1202
1203		// Store block hash in pallet-revive BlockHash mapping
1204		crate::BlockHash::<T>::insert(crate::BlockNumberFor::<T>::from(0u32), block_hash);
1205
1206		let result;
1207		#[block]
1208		{
1209			result = runtime.bench_block_hash(memory.as_mut_slice(), 32, 0);
1210		}
1211		assert_ok!(result);
1212		assert_eq!(&memory[..32], &block_hash.0);
1213	}
1214
1215	#[benchmark(pov_mode = Measured)]
1216	fn seal_now() {
1217		build_runtime!(runtime, memory: [[0u8;32], ]);
1218		let result;
1219		#[block]
1220		{
1221			result = runtime.bench_now(memory.as_mut_slice(), 0);
1222		}
1223		assert_ok!(result);
1224		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().now());
1225	}
1226
1227	#[benchmark(pov_mode = Measured)]
1228	fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) {
1229		let mut setup = CallSetup::<T>::default();
1230		let (mut ext, _) = setup.ext();
1231		let mut runtime = pvm::Runtime::new(&mut ext, vec![]);
1232		let mut memory = memory!(n.encode(), vec![0u8; n as usize],);
1233		let result;
1234		#[block]
1235		{
1236			result = runtime.write_sandbox_output(
1237				memory.as_mut_slice(),
1238				4,
1239				0,
1240				&vec![42u8; n as usize],
1241				false,
1242				|_| None,
1243			);
1244		}
1245		assert_ok!(result);
1246		assert_eq!(&memory[..4], &n.encode());
1247		assert_eq!(&memory[4..], &vec![42u8; n as usize]);
1248	}
1249
1250	#[benchmark(pov_mode = Measured)]
1251	fn seal_call_data_load() {
1252		let mut setup = CallSetup::<T>::default();
1253		let (mut ext, _) = setup.ext();
1254		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 32]);
1255		let mut memory = memory!(vec![0u8; 32],);
1256		let result;
1257		#[block]
1258		{
1259			result = runtime.bench_call_data_load(memory.as_mut_slice(), 0, 0);
1260		}
1261		assert_ok!(result);
1262		assert_eq!(&memory[..], &vec![42u8; 32]);
1263	}
1264
1265	#[benchmark(pov_mode = Measured)]
1266	fn seal_call_data_copy(n: Linear<0, { limits::code::BLOB_BYTES }>) {
1267		let mut setup = CallSetup::<T>::default();
1268		let (mut ext, _) = setup.ext();
1269		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; n as usize]);
1270		let mut memory = memory!(vec![0u8; n as usize],);
1271		let result;
1272		#[block]
1273		{
1274			result = runtime.bench_call_data_copy(memory.as_mut_slice(), 0, n, 0);
1275		}
1276		assert_ok!(result);
1277		assert_eq!(&memory[..], &vec![42u8; n as usize]);
1278	}
1279
1280	#[benchmark(pov_mode = Measured)]
1281	fn seal_return(n: Linear<0, { limits::CALLDATA_BYTES }>) {
1282		build_runtime!(runtime, memory: [n.to_le_bytes(), vec![42u8; n as usize], ]);
1283
1284		let result;
1285		#[block]
1286		{
1287			result = runtime.bench_seal_return(memory.as_mut_slice(), 0, 0, n);
1288		}
1289
1290		assert!(matches!(
1291			result,
1292			Err(crate::vm::pvm::TrapReason::Return(crate::vm::pvm::ReturnData { .. }))
1293		));
1294	}
1295
1296	/// Benchmark the ocst of terminating a contract.
1297	///
1298	/// `r`: whether the old code will be removed as a result of this operation. (1: yes, 0: no)
1299	#[benchmark(pov_mode = Measured)]
1300	fn seal_terminate(r: Linear<0, 1>) -> Result<(), BenchmarkError> {
1301		let delete_code = r == 1;
1302		let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
1303
1304		build_runtime!(runtime, instance, memory: [beneficiary.encode(),]);
1305		let code_hash = instance.info()?.code_hash;
1306
1307		// Increment the refcount of the code hash so that it does not get deleted
1308		if !delete_code {
1309			<CodeInfo<T>>::increment_refcount(code_hash).unwrap();
1310		}
1311
1312		let result;
1313		#[block]
1314		{
1315			result = runtime.bench_terminate(memory.as_mut_slice(), 0);
1316		}
1317
1318		assert!(matches!(result, Err(crate::vm::pvm::TrapReason::Termination)));
1319
1320		Ok(())
1321	}
1322
1323	#[benchmark(pov_mode = Measured)]
1324	fn seal_terminate_logic() -> Result<(), BenchmarkError> {
1325		let caller = whitelisted_caller();
1326		let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
1327		T::AddressMapper::map_no_deposit_unchecked(&beneficiary)?;
1328
1329		build_runtime!(_runtime, instance, _memory: [vec![0u8; 0], ]);
1330		let code_hash = instance.info()?.code_hash;
1331
1332		assert!(PristineCode::<T>::get(code_hash).is_some());
1333
1334		T::Currency::set_balance(&instance.account_id, Pallet::<T>::min_balance() * 10u32.into());
1335
1336		let storage_deposit = T::Currency::balance_on_hold(
1337			&HoldReason::StorageDepositReserve.into(),
1338			&instance.account_id,
1339		);
1340		NativeDepositOf::<T>::insert(&instance.account_id, &caller, storage_deposit);
1341
1342		let mut transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit {
1343			weight_limit: Default::default(),
1344			deposit_limit: BalanceOf::<T>::max_value(),
1345		})
1346		.unwrap();
1347		let exec_config = ExecConfig::new_substrate_tx();
1348		let contract_account = &instance.account_id;
1349		let origin = &ExecOrigin::from_account_id(caller);
1350		let beneficiary_clone = beneficiary.clone();
1351		let trie_id = instance.info()?.trie_id.clone();
1352		let code_hash = instance.info()?.code_hash;
1353		let only_if_same_tx = false;
1354
1355		let result;
1356		#[block]
1357		{
1358			result = crate::exec::bench_do_terminate::<T>(
1359				&mut transaction_meter,
1360				&exec_config,
1361				contract_account,
1362				&origin,
1363				beneficiary_clone,
1364				trie_id,
1365				code_hash,
1366				only_if_same_tx,
1367			);
1368		}
1369		result.unwrap();
1370
1371		// Check that the contract is removed
1372		assert!(PristineCode::<T>::get(code_hash).is_none());
1373
1374		// Check that the balance has been transferred away
1375		let balance = <T as Config>::Currency::total_balance(&instance.account_id);
1376		assert_eq!(balance, 0u32.into());
1377
1378		// Check that the beneficiary received the balance
1379		let balance = <T as Config>::Currency::balance(&beneficiary);
1380		assert_eq!(balance, Pallet::<T>::min_balance() + Pallet::<T>::min_balance() * 9u32.into());
1381
1382		Ok(())
1383	}
1384
1385	// Benchmark the overhead that topics generate.
1386	// `t`: Number of topics
1387	// `n`: Size of event payload in bytes
1388	#[benchmark(pov_mode = Measured)]
1389	fn seal_deposit_event(
1390		t: Linear<0, { limits::NUM_EVENT_TOPICS as u32 }>,
1391		n: Linear<0, { limits::EVENT_BYTES }>,
1392	) {
1393		let num_topic = t as u32;
1394		let topics = (0..t).map(|i| H256::repeat_byte(i as u8)).collect::<Vec<_>>();
1395		let topics_data =
1396			topics.iter().flat_map(|hash| hash.as_bytes().to_vec()).collect::<Vec<u8>>();
1397		let data = vec![42u8; n as _];
1398		build_runtime!(runtime, instance, memory: [ topics_data, data, ]);
1399
1400		let result;
1401		#[block]
1402		{
1403			result = runtime.bench_deposit_event(
1404				memory.as_mut_slice(),
1405				0, // topics_ptr
1406				num_topic,
1407				topics_data.len() as u32, // data_ptr
1408				n,                        // data_len
1409			);
1410		}
1411		assert_ok!(result);
1412
1413		let events = System::<T>::events();
1414		let record = &events[events.len() - 1];
1415
1416		assert_eq!(
1417			record.event,
1418			crate::Event::ContractEmitted { contract: instance.address, data, topics }.into(),
1419		);
1420	}
1421
1422	enum TrieFill {
1423		Empty,
1424		Full,
1425	}
1426
1427	enum SlotAccess {
1428		Cold,
1429		Hot,
1430	}
1431
1432	enum StorageOp {
1433		Read,
1434		Write,
1435	}
1436
1437	fn build_storage_contract<T: Config>(
1438		op: StorageOp,
1439		fill: TrieFill,
1440	) -> Result<(ContractInfo<T>, Vec<u8>, Vec<u8>), BenchmarkError> {
1441		let key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1442		let value = vec![1u8; limits::STORAGE_BYTES as usize];
1443		let initial_value = match op {
1444			StorageOp::Read => value.clone(),
1445			StorageOp::Write => vec![42u8; limits::STORAGE_BYTES as usize],
1446		};
1447
1448		let instance = match fill {
1449			TrieFill::Full => {
1450				Contract::<T>::with_unbalanced_storage_trie(VmBinaryModule::dummy(), &key)?
1451			},
1452			TrieFill::Empty => Contract::<T>::new(VmBinaryModule::dummy(), vec![])?,
1453		};
1454		let info = instance.info()?;
1455		info.bench_write_raw(&key, Some(initial_value), false)
1456			.map_err(|_| "Failed to write to storage during setup.")?;
1457		Ok((info, key, value))
1458	}
1459
1460	enum StorageCall {
1461		Clear,
1462		Contains,
1463		Take,
1464	}
1465
1466	fn setup_precompile_bench<T: Config>(
1467		op: StorageCall,
1468		key_byte: u8,
1469		access: SlotAccess,
1470	) -> Result<(CallSetup<T>, Key, Vec<u8>), BenchmarkError> {
1471		let max_key_len = limits::STORAGE_KEY_BYTES;
1472		let key = Key::try_from_var(vec![key_byte; max_key_len as usize])
1473			.map_err(|_| "Key has wrong length")?;
1474		let raw_key = vec![key_byte; max_key_len as usize].into();
1475		let input_bytes = match op {
1476			StorageCall::Clear => {
1477				IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
1478					flags: StorageFlags::empty().bits(),
1479					key: raw_key,
1480					isFixedKey: false,
1481				})
1482			},
1483			StorageCall::Contains => {
1484				IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
1485					flags: StorageFlags::empty().bits(),
1486					key: raw_key,
1487					isFixedKey: false,
1488				})
1489			},
1490			StorageCall::Take => IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
1491				flags: StorageFlags::empty().bits(),
1492				key: raw_key,
1493				isFixedKey: false,
1494			}),
1495		}
1496		.abi_encode();
1497
1498		let call_setup = CallSetup::<T>::default();
1499		if matches!(access, SlotAccess::Hot) {
1500			let info = call_setup.contract().info()?;
1501			frame_benchmarking::add_to_whitelist_child(
1502				info.child_trie_info().storage_key().to_vec(),
1503				key.hash(),
1504			);
1505		}
1506		Ok((call_setup, key, input_bytes))
1507	}
1508
1509	#[benchmark(skip_meta, pov_mode = Measured)]
1510	fn get_storage_empty() -> Result<(), BenchmarkError> {
1511		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Empty)?;
1512		let child_trie_info = info.child_trie_info();
1513
1514		let result;
1515		#[block]
1516		{
1517			result = child::get_raw(&child_trie_info, &key);
1518		}
1519
1520		assert_eq!(result, Some(value));
1521		Ok(())
1522	}
1523
1524	#[benchmark(skip_meta, pov_mode = Measured)]
1525	fn get_storage_full() -> Result<(), BenchmarkError> {
1526		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Full)?;
1527		let child_trie_info = info.child_trie_info();
1528
1529		let result;
1530		#[block]
1531		{
1532			result = child::get_raw(&child_trie_info, &key);
1533		}
1534
1535		assert_eq!(result, Some(value));
1536		Ok(())
1537	}
1538
1539	#[benchmark(skip_meta, pov_mode = Measured)]
1540	fn set_storage_empty() -> Result<(), BenchmarkError> {
1541		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Empty)?;
1542
1543		let val = Some(value.clone());
1544		let result;
1545		#[block]
1546		{
1547			result = info.bench_write_raw(&key, val, true);
1548		}
1549
1550		assert_ok!(result);
1551		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1552		Ok(())
1553	}
1554
1555	#[benchmark(skip_meta, pov_mode = Measured)]
1556	fn set_storage_full() -> Result<(), BenchmarkError> {
1557		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Full)?;
1558
1559		let val = Some(value.clone());
1560		let result;
1561		#[block]
1562		{
1563			result = info.bench_write_raw(&key, val, true);
1564		}
1565
1566		assert_ok!(result);
1567		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1568		Ok(())
1569	}
1570
1571	/// Worst-case lookup keys: differing only at the tail, comparisons walk the whole key.
1572	fn shared_prefix_keys(count: usize, suffix_of: impl Fn(u64) -> u64) -> Vec<Vec<u8>> {
1573		(0..count as u64)
1574			.map(|i| {
1575				let mut key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1576				key[limits::STORAGE_KEY_BYTES as usize - 8..]
1577					.copy_from_slice(&suffix_of(i).to_be_bytes());
1578				key
1579			})
1580			.collect()
1581	}
1582
1583	/// Probed keys must exist, or reads measure a proof-of-absence walk instead.
1584	/// Whitelisting keeps them out of the DB counters and pre-warms the cache.
1585	fn setup_stored_keys<T: Config>(
1586		count: usize,
1587		value_byte: u8,
1588		suffix_of: impl Fn(u64) -> u64,
1589	) -> Result<(ContractInfo<T>, Vec<Vec<u8>>), BenchmarkError> {
1590		let instance = Contract::<T>::new(VmBinaryModule::dummy(), vec![])?;
1591		let info = instance.info()?;
1592		let child_trie_info = info.child_trie_info();
1593		let value = vec![value_byte; limits::STORAGE_BYTES as usize];
1594		let stored_keys = shared_prefix_keys(count, suffix_of);
1595		for key in &stored_keys {
1596			info.bench_write_raw(key, Some(value.clone()), false)
1597				.map_err(|_| "Failed to write to storage during setup.")?;
1598			frame_benchmarking::add_to_whitelist_child(
1599				child_trie_info.storage_key().to_vec(),
1600				key.clone(),
1601			);
1602		}
1603		Ok((info, stored_keys))
1604	}
1605
1606	#[benchmark(skip_meta, pov_mode = Measured)]
1607	fn overlay_probe_full(
1608		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
1609	) -> Result<(), BenchmarkError> {
1610		let value_byte = 42;
1611		// One stored key per probe; odd suffixes, so the even fill never overwrites them.
1612		let (info, stored_keys) =
1613			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
1614		let child_trie_info = info.child_trie_info();
1615		// The block's PoV budget bounds the overlay entries like the access list cap.
1616		let fill_keys = shared_prefix_keys(MAX_ACCESS_LIST_ENTRIES, |i| (i + 1) * 2);
1617
1618		let mut result = None;
1619		#[block]
1620		{
1621			// The benchmark framework drains the overlay right before the block, so fill here.
1622			for key in &fill_keys {
1623				child::put_raw(&child_trie_info, key, &[0u8]);
1624			}
1625			for i in 0..n {
1626				let index = i as usize % stored_keys.len();
1627				result = child::get_raw(&child_trie_info, &stored_keys[index]);
1628			}
1629		}
1630
1631		if n > 0 {
1632			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
1633			assert_eq!(result, Some(expected), "the stored value must be read back");
1634		}
1635		Ok(())
1636	}
1637
1638	#[benchmark(skip_meta, pov_mode = Measured)]
1639	fn overlay_probe_empty(
1640		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
1641	) -> Result<(), BenchmarkError> {
1642		let value_byte = 42;
1643		// Reads must cost the same as in `overlay_probe_full`, so their shared cost cancels.
1644		let (info, stored_keys) =
1645			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
1646		let child_trie_info = info.child_trie_info();
1647
1648		let mut result = None;
1649		#[block]
1650		{
1651			for i in 0..n {
1652				let index = i as usize % stored_keys.len();
1653				result = child::get_raw(&child_trie_info, &stored_keys[index]);
1654			}
1655		}
1656
1657		if n > 0 {
1658			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
1659			assert_eq!(result, Some(expected), "the stored value must be read back");
1660		}
1661		Ok(())
1662	}
1663
1664	// n: new byte size
1665	// o: old byte size
1666	#[benchmark(skip_meta, pov_mode = Measured)]
1667	fn seal_set_storage(
1668		n: Linear<0, { limits::STORAGE_BYTES }>,
1669		o: Linear<0, { limits::STORAGE_BYTES }>,
1670	) -> Result<(), BenchmarkError> {
1671		let max_key_len = limits::STORAGE_KEY_BYTES;
1672		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1673			.map_err(|_| "Key has wrong length")?;
1674		let value = vec![1u8; n as usize];
1675
1676		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
1677		let info = instance.info()?;
1678
1679		info.write(&key, Some(vec![42u8; o as usize]), None, false)
1680			.map_err(|_| "Failed to write to storage during setup.")?;
1681
1682		let result;
1683		#[block]
1684		{
1685			result = runtime.bench_set_storage(
1686				memory.as_mut_slice(),
1687				StorageFlags::empty().bits(),
1688				0,           // key_ptr
1689				max_key_len, // key_len
1690				max_key_len, // value_ptr
1691				n,           // value_len
1692			);
1693		}
1694
1695		assert_ok!(result);
1696		assert_eq!(info.read(&key).unwrap(), value);
1697		Ok(())
1698	}
1699
1700	#[benchmark(skip_meta, pov_mode = Measured)]
1701	fn seal_set_storage_hot(
1702		n: Linear<0, { limits::STORAGE_BYTES }>,
1703		o: Linear<0, { limits::STORAGE_BYTES }>,
1704	) -> Result<(), BenchmarkError> {
1705		let max_key_len = limits::STORAGE_KEY_BYTES;
1706		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1707			.map_err(|_| "Key has wrong length")?;
1708		let value = vec![1u8; n as usize];
1709
1710		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
1711		let info = instance.info()?;
1712
1713		info.write(&key, Some(vec![42u8; o as usize]), None, false)
1714			.map_err(|_| "Failed to write to storage during setup.")?;
1715
1716		frame_benchmarking::add_to_whitelist_child(
1717			info.child_trie_info().storage_key().to_vec(),
1718			key.hash(),
1719		);
1720
1721		// Add the key to access list so the op's touch is hot.
1722		runtime.ext().touch_storage_access(false, &key);
1723
1724		let result;
1725		#[block]
1726		{
1727			result = runtime.bench_set_storage(
1728				memory.as_mut_slice(),
1729				StorageFlags::empty().bits(),
1730				0,           // key_ptr
1731				max_key_len, // key_len
1732				max_key_len, // value_ptr
1733				n,           // value_len
1734			);
1735		}
1736
1737		assert_ok!(result);
1738		assert_eq!(info.read(&key).unwrap(), value);
1739		Ok(())
1740	}
1741
1742	#[benchmark(skip_meta, pov_mode = Measured)]
1743	fn clear_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1744		let key_byte = 0;
1745		let (mut call_setup, key, input_bytes) =
1746			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Cold)?;
1747		let (mut ext, _) = call_setup.ext();
1748		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1749			.map_err(|_| "Failed to write to storage during setup.")?;
1750
1751		let result;
1752		#[block]
1753		{
1754			result = run_builtin_precompile(
1755				&mut ext,
1756				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1757				input_bytes,
1758			);
1759		}
1760		assert_ok!(result);
1761		assert!(ext.get_storage(&key).is_none());
1762
1763		Ok(())
1764	}
1765
1766	#[benchmark(skip_meta, pov_mode = Measured)]
1767	fn clear_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1768		let key_byte = 0;
1769		let (mut call_setup, key, input_bytes) =
1770			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Hot)?;
1771		let (mut ext, _) = call_setup.ext();
1772		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1773			.map_err(|_| "Failed to write to storage during setup.")?;
1774
1775		ext.touch_storage_access(false, &key);
1776
1777		let result;
1778		#[block]
1779		{
1780			result = run_builtin_precompile(
1781				&mut ext,
1782				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1783				input_bytes,
1784			);
1785		}
1786		assert_ok!(result);
1787		assert!(ext.get_storage(&key).is_none());
1788
1789		Ok(())
1790	}
1791
1792	#[benchmark(skip_meta, pov_mode = Measured)]
1793	fn seal_get_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1794		let max_key_len = limits::STORAGE_KEY_BYTES;
1795		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1796			.map_err(|_| "Key has wrong length")?;
1797		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
1798		let info = instance.info()?;
1799
1800		info.write(&key, Some(vec![42u8; n as usize]), None, false)
1801			.map_err(|_| "Failed to write to storage during setup.")?;
1802
1803		let out_ptr = max_key_len + 4;
1804		let result;
1805		#[block]
1806		{
1807			result = runtime.bench_get_storage(
1808				memory.as_mut_slice(),
1809				StorageFlags::empty().bits(),
1810				0,           // key_ptr
1811				max_key_len, // key_len
1812				out_ptr,     // out_ptr
1813				max_key_len, // out_len_ptr
1814			);
1815		}
1816
1817		assert_ok!(result);
1818		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
1819		Ok(())
1820	}
1821
1822	#[benchmark(skip_meta, pov_mode = Measured)]
1823	fn seal_get_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1824		let max_key_len = limits::STORAGE_KEY_BYTES;
1825		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1826			.map_err(|_| "Key has wrong length")?;
1827		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
1828		let info = instance.info()?;
1829
1830		info.write(&key, Some(vec![42u8; n as usize]), None, false)
1831			.map_err(|_| "Failed to write to storage during setup.")?;
1832
1833		frame_benchmarking::add_to_whitelist_child(
1834			info.child_trie_info().storage_key().to_vec(),
1835			key.hash(),
1836		);
1837
1838		runtime.ext().touch_storage_access(false, &key);
1839
1840		let out_ptr = max_key_len + 4;
1841		let result;
1842		#[block]
1843		{
1844			result = runtime.bench_get_storage(
1845				memory.as_mut_slice(),
1846				StorageFlags::empty().bits(),
1847				0,           // key_ptr
1848				max_key_len, // key_len
1849				out_ptr,     // out_ptr
1850				max_key_len, // out_len_ptr
1851			);
1852		}
1853
1854		assert_ok!(result);
1855		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
1856		Ok(())
1857	}
1858
1859	#[benchmark(skip_meta, pov_mode = Measured)]
1860	fn contains_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1861		let key_byte = 0;
1862		let (mut call_setup, key, input_bytes) =
1863			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Cold)?;
1864		let (mut ext, _) = call_setup.ext();
1865		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1866			.map_err(|_| "Failed to write to storage during setup.")?;
1867
1868		let result;
1869		#[block]
1870		{
1871			result = run_builtin_precompile(
1872				&mut ext,
1873				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1874				input_bytes,
1875			);
1876		}
1877		assert_ok!(result);
1878		assert!(ext.get_storage(&key).is_some());
1879
1880		Ok(())
1881	}
1882
1883	#[benchmark(skip_meta, pov_mode = Measured)]
1884	fn contains_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1885		let key_byte = 0;
1886		let (mut call_setup, key, input_bytes) =
1887			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Hot)?;
1888		let (mut ext, _) = call_setup.ext();
1889		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1890			.map_err(|_| "Failed to write to storage during setup.")?;
1891
1892		ext.touch_storage_access(false, &key);
1893
1894		let result;
1895		#[block]
1896		{
1897			result = run_builtin_precompile(
1898				&mut ext,
1899				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1900				input_bytes,
1901			);
1902		}
1903		assert_ok!(result);
1904		assert!(ext.get_storage(&key).is_some());
1905
1906		Ok(())
1907	}
1908
1909	#[benchmark(skip_meta, pov_mode = Measured)]
1910	fn take_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1911		let key_byte = 3;
1912		let (mut call_setup, key, input_bytes) =
1913			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Cold)?;
1914		let (mut ext, _) = call_setup.ext();
1915		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1916			.map_err(|_| "Failed to write to storage during setup.")?;
1917
1918		let result;
1919		#[block]
1920		{
1921			result = run_builtin_precompile(
1922				&mut ext,
1923				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1924				input_bytes,
1925			);
1926		}
1927		assert_ok!(result);
1928		assert!(ext.get_storage(&key).is_none());
1929
1930		Ok(())
1931	}
1932
1933	#[benchmark(skip_meta, pov_mode = Measured)]
1934	fn take_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1935		let key_byte = 3;
1936		let (mut call_setup, key, input_bytes) =
1937			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Hot)?;
1938		let (mut ext, _) = call_setup.ext();
1939		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1940			.map_err(|_| "Failed to write to storage during setup.")?;
1941
1942		ext.touch_storage_access(false, &key);
1943
1944		let result;
1945		#[block]
1946		{
1947			result = run_builtin_precompile(
1948				&mut ext,
1949				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1950				input_bytes,
1951			);
1952		}
1953		assert_ok!(result);
1954		assert!(ext.get_storage(&key).is_none());
1955
1956		Ok(())
1957	}
1958
1959	fn worst_case_slot() -> crate::access_list::Slot {
1960		let key = Key::try_from_var(vec![0xFFu8; limits::STORAGE_KEY_BYTES as usize])
1961			.expect("key fits STORAGE_KEY_BYTES bound; qed");
1962		crate::access_list::Slot::from(&key)
1963	}
1964
1965	fn near_full_access_list() -> crate::access_list::AccessList {
1966		let mut al = AccessList::new();
1967		for i in 0..(MAX_ACCESS_LIST_ENTRIES - 1) {
1968			al.touch(AccessEntry {
1969				slot: worst_case_slot(),
1970				address: H160::from_low_u64_be(i as u64),
1971			});
1972		}
1973		al
1974	}
1975
1976	#[benchmark(pov_mode = Ignored)]
1977	fn access_list_touch_cold_full() -> Result<(), BenchmarkError> {
1978		let mut al = near_full_access_list();
1979		// Insert a new entry (u64::MAX is past the fill range, so the touch is cold).
1980		let entry =
1981			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
1982		let outcome;
1983		#[block]
1984		{
1985			outcome = al.touch(entry);
1986		}
1987		assert!(outcome.is_cold());
1988		Ok(())
1989	}
1990
1991	#[benchmark(pov_mode = Ignored)]
1992	fn access_list_touch_hot_full() -> Result<(), BenchmarkError> {
1993		let mut al = near_full_access_list();
1994		// Re-touch an entry that is already present (address zero is in the fill range).
1995		let entry = AccessEntry { slot: worst_case_slot(), address: H160::zero() };
1996		let outcome;
1997		#[block]
1998		{
1999			outcome = al.touch(entry);
2000		}
2001		assert!(!outcome.is_cold());
2002		Ok(())
2003	}
2004
2005	#[benchmark(pov_mode = Ignored)]
2006	fn access_list_touch_cold_empty() -> Result<(), BenchmarkError> {
2007		let mut al = AccessList::new();
2008		let entry =
2009			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2010		let outcome;
2011		#[block]
2012		{
2013			outcome = al.touch(entry);
2014		}
2015		assert!(outcome.is_cold());
2016		Ok(())
2017	}
2018
2019	#[benchmark(pov_mode = Ignored)]
2020	fn access_list_touch_hot_single_element() -> Result<(), BenchmarkError> {
2021		let mut al = AccessList::new();
2022		let entry =
2023			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2024		al.touch(entry.clone());
2025		let outcome;
2026		#[block]
2027		{
2028			outcome = al.touch(entry);
2029		}
2030		assert!(!outcome.is_cold());
2031		Ok(())
2032	}
2033
2034	// Per-entry rollback cost, prepaid by every cold touch since a frame revert
2035	// can't charge gas itself. Isolated by reverting a frame with exactly one
2036	// journaled entry on top of a near-full `AccessList`.
2037	#[benchmark(pov_mode = Ignored)]
2038	fn access_list_rollback_amortization() -> Result<(), BenchmarkError> {
2039		let mut al = near_full_access_list();
2040		al.enter_frame();
2041		al.touch(AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) });
2042		#[block]
2043		{
2044			al.rollback_frame();
2045		}
2046		Ok(())
2047	}
2048
2049	// We use both full and empty benchmarks here instead of benchmarking transient_storage
2050	// (BTreeMap) directly. This approach is necessary because benchmarking this BTreeMap is very
2051	// slow. Additionally, we use linear regression for our benchmarks, and the BTreeMap's log(n)
2052	// complexity can introduce approximation errors.
2053	#[benchmark(pov_mode = Ignored)]
2054	fn set_transient_storage_empty() -> Result<(), BenchmarkError> {
2055		let max_value_len = limits::STORAGE_BYTES;
2056		let max_key_len = limits::STORAGE_KEY_BYTES;
2057		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2058			.map_err(|_| "Key has wrong length")?;
2059		let value = Some(vec![42u8; max_value_len as _]);
2060		let mut setup = CallSetup::<T>::default();
2061		let (mut ext, _) = setup.ext();
2062		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2063		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2064		let result;
2065		#[block]
2066		{
2067			result = runtime.ext().set_transient_storage(&key, value, false);
2068		}
2069
2070		assert_eq!(result, Ok(WriteOutcome::New));
2071		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2072		Ok(())
2073	}
2074
2075	#[benchmark(pov_mode = Ignored)]
2076	fn set_transient_storage_full() -> Result<(), BenchmarkError> {
2077		let max_value_len = limits::STORAGE_BYTES;
2078		let max_key_len = limits::STORAGE_KEY_BYTES;
2079		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2080			.map_err(|_| "Key has wrong length")?;
2081		let value = Some(vec![42u8; max_value_len as _]);
2082		let mut setup = CallSetup::<T>::default();
2083		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2084		let (mut ext, _) = setup.ext();
2085		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2086		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2087		let result;
2088		#[block]
2089		{
2090			result = runtime.ext().set_transient_storage(&key, value, false);
2091		}
2092
2093		assert_eq!(result, Ok(WriteOutcome::New));
2094		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2095		Ok(())
2096	}
2097
2098	#[benchmark(pov_mode = Ignored)]
2099	fn get_transient_storage_empty() -> Result<(), BenchmarkError> {
2100		let max_value_len = limits::STORAGE_BYTES;
2101		let max_key_len = limits::STORAGE_KEY_BYTES;
2102		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2103			.map_err(|_| "Key has wrong length")?;
2104
2105		let mut setup = CallSetup::<T>::default();
2106		let (mut ext, _) = setup.ext();
2107		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2108		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2109		runtime
2110			.ext()
2111			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2112			.map_err(|_| "Failed to write to transient storage during setup.")?;
2113		let result;
2114		#[block]
2115		{
2116			result = runtime.ext().get_transient_storage(&key);
2117		}
2118
2119		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2120		Ok(())
2121	}
2122
2123	#[benchmark(pov_mode = Ignored)]
2124	fn get_transient_storage_full() -> Result<(), BenchmarkError> {
2125		let max_value_len = limits::STORAGE_BYTES;
2126		let max_key_len = limits::STORAGE_KEY_BYTES;
2127		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2128			.map_err(|_| "Key has wrong length")?;
2129
2130		let mut setup = CallSetup::<T>::default();
2131		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2132		let (mut ext, _) = setup.ext();
2133		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2134		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2135		runtime
2136			.ext()
2137			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2138			.map_err(|_| "Failed to write to transient storage during setup.")?;
2139		let result;
2140		#[block]
2141		{
2142			result = runtime.ext().get_transient_storage(&key);
2143		}
2144
2145		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2146		Ok(())
2147	}
2148
2149	// The weight of journal rollbacks should be taken into account when setting storage.
2150	#[benchmark(pov_mode = Ignored)]
2151	fn rollback_transient_storage() -> Result<(), BenchmarkError> {
2152		let max_value_len = limits::STORAGE_BYTES;
2153		let max_key_len = limits::STORAGE_KEY_BYTES;
2154		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2155			.map_err(|_| "Key has wrong length")?;
2156
2157		let mut setup = CallSetup::<T>::default();
2158		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2159		let (mut ext, _) = setup.ext();
2160		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2161		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2162		runtime.ext().transient_storage().start_transaction();
2163		runtime
2164			.ext()
2165			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2166			.map_err(|_| "Failed to write to transient storage during setup.")?;
2167		#[block]
2168		{
2169			runtime.ext().transient_storage().rollback_transaction();
2170		}
2171
2172		assert_eq!(runtime.ext().get_transient_storage(&key), None);
2173		Ok(())
2174	}
2175
2176	// n: new byte size
2177	// o: old byte size
2178	#[benchmark(pov_mode = Measured)]
2179	fn seal_set_transient_storage(
2180		n: Linear<0, { limits::STORAGE_BYTES }>,
2181		o: Linear<0, { limits::STORAGE_BYTES }>,
2182	) -> Result<(), BenchmarkError> {
2183		let max_key_len = limits::STORAGE_KEY_BYTES;
2184		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2185			.map_err(|_| "Key has wrong length")?;
2186		let value = vec![1u8; n as usize];
2187		build_runtime!(runtime, memory: [ key.unhashed(), value.clone(), ]);
2188		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2189		runtime
2190			.ext()
2191			.set_transient_storage(&key, Some(vec![42u8; o as usize]), false)
2192			.map_err(|_| "Failed to write to transient storage during setup.")?;
2193
2194		let result;
2195		#[block]
2196		{
2197			result = runtime.bench_set_storage(
2198				memory.as_mut_slice(),
2199				StorageFlags::TRANSIENT.bits(),
2200				0,           // key_ptr
2201				max_key_len, // key_len
2202				max_key_len, // value_ptr
2203				n,           // value_len
2204			);
2205		}
2206
2207		assert_ok!(result);
2208		assert_eq!(runtime.ext().get_transient_storage(&key).unwrap(), value);
2209		Ok(())
2210	}
2211
2212	#[benchmark(pov_mode = Measured)]
2213	fn seal_clear_transient_storage(
2214		n: Linear<0, { limits::STORAGE_BYTES }>,
2215	) -> Result<(), BenchmarkError> {
2216		let max_key_len = limits::STORAGE_KEY_BYTES;
2217		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2218			.map_err(|_| "Key has wrong length")?;
2219		let input_bytes = IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
2220			flags: StorageFlags::TRANSIENT.bits(),
2221			key: vec![0u8; max_key_len as usize].into(),
2222			isFixedKey: false,
2223		})
2224		.abi_encode();
2225
2226		let mut call_setup = CallSetup::<T>::default();
2227		let (mut ext, _) = call_setup.ext();
2228		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2229			.map_err(|_| "Failed to write to transient storage during setup.")?;
2230
2231		let result;
2232		#[block]
2233		{
2234			result = run_builtin_precompile(
2235				&mut ext,
2236				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2237				input_bytes,
2238			);
2239		}
2240		assert_ok!(result);
2241		assert!(ext.get_transient_storage(&key).is_none());
2242
2243		Ok(())
2244	}
2245
2246	#[benchmark(pov_mode = Measured)]
2247	fn seal_get_transient_storage(
2248		n: Linear<0, { limits::STORAGE_BYTES }>,
2249	) -> Result<(), BenchmarkError> {
2250		let max_key_len = limits::STORAGE_KEY_BYTES;
2251		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2252			.map_err(|_| "Key has wrong length")?;
2253		build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2254		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2255		runtime
2256			.ext()
2257			.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2258			.map_err(|_| "Failed to write to transient storage during setup.")?;
2259
2260		let out_ptr = max_key_len + 4;
2261		let result;
2262		#[block]
2263		{
2264			result = runtime.bench_get_storage(
2265				memory.as_mut_slice(),
2266				StorageFlags::TRANSIENT.bits(),
2267				0,           // key_ptr
2268				max_key_len, // key_len
2269				out_ptr,     // out_ptr
2270				max_key_len, // out_len_ptr
2271			);
2272		}
2273
2274		assert_ok!(result);
2275		assert_eq!(
2276			&runtime.ext().get_transient_storage(&key).unwrap(),
2277			&memory[out_ptr as usize..]
2278		);
2279		Ok(())
2280	}
2281
2282	#[benchmark(pov_mode = Measured)]
2283	fn seal_contains_transient_storage(
2284		n: Linear<0, { limits::STORAGE_BYTES }>,
2285	) -> Result<(), BenchmarkError> {
2286		let max_key_len = limits::STORAGE_KEY_BYTES;
2287		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2288			.map_err(|_| "Key has wrong length")?;
2289
2290		let input_bytes = IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
2291			flags: StorageFlags::TRANSIENT.bits(),
2292			key: vec![0u8; max_key_len as usize].into(),
2293			isFixedKey: false,
2294		})
2295		.abi_encode();
2296
2297		let mut call_setup = CallSetup::<T>::default();
2298		let (mut ext, _) = call_setup.ext();
2299		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2300			.map_err(|_| "Failed to write to transient storage during setup.")?;
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!(result.is_ok());
2312		assert!(ext.get_transient_storage(&key).is_some());
2313
2314		Ok(())
2315	}
2316
2317	#[benchmark(pov_mode = Measured)]
2318	fn seal_take_transient_storage(
2319		n: Linear<0, { limits::STORAGE_BYTES }>,
2320	) -> Result<(), BenchmarkError> {
2321		let n = limits::STORAGE_BYTES;
2322		let value = vec![42u8; n as usize];
2323		let max_key_len = limits::STORAGE_KEY_BYTES;
2324		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2325			.map_err(|_| "Key has wrong length")?;
2326
2327		let input_bytes = IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
2328			flags: StorageFlags::TRANSIENT.bits(),
2329			key: vec![0u8; max_key_len as usize].into(),
2330			isFixedKey: false,
2331		})
2332		.abi_encode();
2333
2334		let mut call_setup = CallSetup::<T>::default();
2335		let (mut ext, _) = call_setup.ext();
2336		ext.set_transient_storage(&key, Some(value), false)
2337			.map_err(|_| "Failed to write to transient storage during setup.")?;
2338
2339		let result;
2340		#[block]
2341		{
2342			result = run_builtin_precompile(
2343				&mut ext,
2344				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2345				input_bytes,
2346			);
2347		}
2348		assert!(result.is_ok());
2349		assert!(ext.get_transient_storage(&key).is_none());
2350
2351		Ok(())
2352	}
2353
2354	// t: with or without some value to transfer
2355	// d: with or without dust value to transfer
2356	// i: size of the input data
2357	#[benchmark(pov_mode = Measured)]
2358	fn seal_call(t: Linear<0, 1>, d: Linear<0, 1>, i: Linear<0, { limits::code::BLOB_BYTES }>) {
2359		let Contract { account_id: callee, address: callee_addr, .. } =
2360			Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
2361
2362		let callee_bytes = callee.encode();
2363		let callee_len = callee_bytes.len() as u32;
2364
2365		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2366		let dust = 100u32 * d;
2367		let evm_value =
2368			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2369		let value_bytes = evm_value.encode();
2370
2371		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2372		let deposit_bytes = Into::<U256>::into(deposit).encode();
2373		let deposit_len = deposit_bytes.len() as u32;
2374
2375		let mut setup = CallSetup::<T>::default();
2376		setup.set_storage_deposit_limit(deposit);
2377		// We benchmark the overhead of cloning the input. Not passing it to the contract.
2378		// This is why we set the input here instead of passig it as pointer to the `bench_call`.
2379		setup.set_data(vec![42; i as usize]);
2380		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2381		setup.set_balance(value + 1u32.into() + Pallet::<T>::min_balance());
2382
2383		let (mut ext, _) = setup.ext();
2384		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2385		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,);
2386
2387		let result;
2388		#[block]
2389		{
2390			result = runtime.bench_call(
2391				memory.as_mut_slice(),
2392				pack_hi_lo(CallFlags::CLONE_INPUT.bits(), 0), // flags + callee
2393				u64::MAX,                                     // ref_time_limit
2394				u64::MAX,                                     // proof_size_limit
2395				pack_hi_lo(callee_len, callee_len + deposit_len), // deposit_ptr + value_pr
2396				pack_hi_lo(0, 0),                             // input len + data ptr
2397				pack_hi_lo(0, SENTINEL),                      // output len + data ptr
2398			);
2399		}
2400
2401		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2402		assert_eq!(
2403			Pallet::<T>::evm_balance(&callee_addr),
2404			evm_value,
2405			"{callee_addr:?} balance should hold {evm_value:?}"
2406		);
2407	}
2408
2409	// d: 1 if the associated pre-compile has a contract info that needs to be loaded
2410	// i: size of the input data
2411	#[benchmark(pov_mode = Measured)]
2412	fn seal_call_precompile(d: Linear<0, 1>, i: Linear<0, { limits::CALLDATA_BYTES - 100 }>) {
2413		use alloy_core::sol_types::SolInterface;
2414		use precompiles::{BenchmarkNoInfo, BenchmarkWithInfo, BuiltinPrecompile, IBenchmarking};
2415
2416		let callee_bytes = if d == 1 {
2417			BenchmarkWithInfo::<T>::MATCHER.base_address().to_vec()
2418		} else {
2419			BenchmarkNoInfo::<T>::MATCHER.base_address().to_vec()
2420		};
2421		let callee_len = callee_bytes.len() as u32;
2422
2423		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2424		let deposit_bytes = Into::<U256>::into(deposit).encode();
2425		let deposit_len = deposit_bytes.len() as u32;
2426
2427		let value: BalanceOf<T> = Zero::zero();
2428		let value_bytes = Into::<U256>::into(value).encode();
2429		let value_len = value_bytes.len() as u32;
2430
2431		let input_bytes = IBenchmarking::IBenchmarkingCalls::bench(IBenchmarking::benchCall {
2432			input: vec![42_u8; i as usize].into(),
2433		})
2434		.abi_encode();
2435		let input_len = input_bytes.len() as u32;
2436
2437		let mut setup = CallSetup::<T>::default();
2438		setup.set_storage_deposit_limit(deposit);
2439
2440		let (mut ext, _) = setup.ext();
2441		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2442		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes, input_bytes,);
2443
2444		let mut do_benchmark = || {
2445			runtime.bench_call(
2446				memory.as_mut_slice(),
2447				pack_hi_lo(0, 0), // flags + callee
2448				u64::MAX,         // ref_time_limit
2449				u64::MAX,         // proof_size_limit
2450				pack_hi_lo(callee_len, callee_len + deposit_len), /* deposit_ptr +
2451				                   * value_pr */
2452				pack_hi_lo(input_len, callee_len + deposit_len + value_len), /* input len +
2453				                                                              * input ptr */
2454				pack_hi_lo(0, SENTINEL), // output len + output ptr
2455			)
2456		};
2457
2458		// first call of the pre-compile will create its contract info and account
2459		// so we make sure to create it
2460		assert_eq!(do_benchmark().unwrap(), ReturnErrorCode::Success);
2461
2462		let result;
2463		#[block]
2464		{
2465			result = do_benchmark();
2466		}
2467
2468		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2469	}
2470
2471	#[benchmark(pov_mode = Measured)]
2472	fn seal_delegate_call() -> Result<(), BenchmarkError> {
2473		let Contract { account_id: address, .. } =
2474			Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
2475
2476		let address_bytes = address.encode();
2477		let address_len = address_bytes.len() as u32;
2478
2479		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2480		let deposit_bytes = Into::<U256>::into(deposit).encode();
2481
2482		let mut setup = CallSetup::<T>::default();
2483		setup.set_storage_deposit_limit(deposit);
2484		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2485
2486		let (mut ext, _) = setup.ext();
2487		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2488		let mut memory = memory!(address_bytes, deposit_bytes,);
2489
2490		let result;
2491		#[block]
2492		{
2493			result = runtime.bench_delegate_call(
2494				memory.as_mut_slice(),
2495				pack_hi_lo(0, 0),        // flags + address ptr
2496				u64::MAX,                // ref_time_limit
2497				u64::MAX,                // proof_size_limit
2498				address_len,             // deposit_ptr
2499				pack_hi_lo(0, 0),        // input len + data ptr
2500				pack_hi_lo(0, SENTINEL), // output len + ptr
2501			);
2502		}
2503
2504		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2505		Ok(())
2506	}
2507
2508	// t: with or without some value to transfer
2509	// d: with or without dust value to transfer
2510	// i: size of the input data
2511	#[benchmark(pov_mode = Measured)]
2512	fn seal_instantiate(
2513		t: Linear<0, 1>,
2514		d: Linear<0, 1>,
2515		i: Linear<0, { limits::CALLDATA_BYTES }>,
2516	) -> Result<(), BenchmarkError> {
2517		let code = VmBinaryModule::dummy();
2518		let hash = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?.info()?.code_hash;
2519		let hash_bytes = hash.encode();
2520
2521		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2522		let dust = 100u32 * d;
2523		let evm_value =
2524			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2525		let value_bytes = evm_value.encode();
2526		let value_len = value_bytes.len() as u32;
2527
2528		let deposit: BalanceOf<T> = BalanceOf::<T>::max_value();
2529		let deposit_bytes = Into::<U256>::into(deposit).encode();
2530		let deposit_len = deposit_bytes.len() as u32;
2531
2532		let mut setup = CallSetup::<T>::default();
2533		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2534		setup.set_balance(value + 1u32.into() + (Pallet::<T>::min_balance() * 2u32.into()));
2535
2536		let account_id = &setup.contract().account_id.clone();
2537		let (mut ext, _) = setup.ext();
2538		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2539
2540		let input = vec![42u8; i as _];
2541		let input_len = hash_bytes.len() as u32 + input.len() as u32;
2542		let salt = [42u8; 32];
2543		let deployer = T::AddressMapper::to_address(&account_id);
2544		let addr = crate::address::create2(&deployer, &code.code, &input, &salt);
2545		let mut memory = memory!(hash_bytes, input, deposit_bytes, value_bytes, salt,);
2546
2547		let mut offset = {
2548			let mut current = 0u32;
2549			move |after: u32| {
2550				current += after;
2551				current
2552			}
2553		};
2554
2555		assert!(AccountInfoOf::<T>::get(&addr).is_none());
2556
2557		let result;
2558		#[block]
2559		{
2560			result = runtime.bench_instantiate(
2561				memory.as_mut_slice(),
2562				u64::MAX,                                           // ref_time_limit
2563				u64::MAX,                                           // proof_size_limit
2564				pack_hi_lo(offset(input_len), offset(deposit_len)), // deposit_ptr + value_ptr
2565				pack_hi_lo(input_len, 0),                           // input_data_len + input_data
2566				pack_hi_lo(0, SENTINEL),                            // output_len_ptr + output_ptr
2567				pack_hi_lo(SENTINEL, offset(value_len)),            // address_ptr + salt_ptr
2568			);
2569		}
2570
2571		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2572		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
2573
2574		assert_eq!(
2575			Pallet::<T>::evm_balance(&addr),
2576			evm_value,
2577			"{addr:?} balance should hold {evm_value:?}"
2578		);
2579		Ok(())
2580	}
2581
2582	// t: with or without some value to transfer
2583	// d: with or without dust value to transfer
2584	// i: size of the init code (max 49152 bytes per EIP-3860)
2585	#[benchmark(pov_mode = Measured)]
2586	fn evm_instantiate(
2587		t: Linear<0, 1>,
2588		d: Linear<0, 1>,
2589		i: Linear<{ 10 * 1024 }, { 48 * 1024 }>,
2590	) -> Result<(), BenchmarkError> {
2591		use crate::vm::evm::instructions::BENCH_INIT_CODE;
2592		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_init_code_for_runtime_size(0));
2593		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2594		setup.set_balance(caller_funding::<T>());
2595
2596		let (mut ext, _) = setup.ext();
2597		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
2598
2599		let value = {
2600			let value: BalanceOf<T> = (1_000_000u32 * t).into();
2601			let dust = 100u32 * d;
2602			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust))
2603		};
2604
2605		let init_code = vec![BENCH_INIT_CODE; i as usize];
2606		let _ = interpreter.memory.resize(0, init_code.len());
2607		let salt = U256::from(42u64);
2608		interpreter.memory.set_data(0, 0, init_code.len(), &init_code);
2609
2610		// Setup stack for create instruction [value, offset, size, salt]
2611		let _ = interpreter.stack.push(salt);
2612		let _ = interpreter.stack.push(U256::from(init_code.len()));
2613		let _ = interpreter.stack.push(U256::zero());
2614		let _ = interpreter.stack.push(value);
2615
2616		let result;
2617		#[block]
2618		{
2619			result = instructions::contract::create::<true, _>(&mut interpreter);
2620		}
2621
2622		assert!(result.is_continue());
2623		let addr = interpreter.stack.top().unwrap().into_address();
2624		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
2625		assert_eq!(Pallet::<T>::code(&addr).len(), revm::primitives::eip170::MAX_CODE_SIZE);
2626		assert_eq!(Pallet::<T>::evm_balance(&addr), value, "balance should hold {value:?}");
2627		Ok(())
2628	}
2629
2630	// `n`: Input to hash in bytes
2631	#[benchmark(pov_mode = Measured)]
2632	fn sha2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2633		let input = vec![0u8; n as usize];
2634		let mut call_setup = CallSetup::<T>::default();
2635		let (mut ext, _) = call_setup.ext();
2636
2637		let result;
2638		#[block]
2639		{
2640			result = run_builtin_precompile(
2641				&mut ext,
2642				H160::from_low_u64_be(2).as_fixed_bytes(),
2643				input.clone(),
2644			);
2645		}
2646		assert_eq!(sp_io::hashing::sha2_256(&input).to_vec(), result.unwrap().data);
2647	}
2648
2649	#[benchmark(pov_mode = Measured)]
2650	fn identity(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2651		let input = vec![0u8; n as usize];
2652		let mut call_setup = CallSetup::<T>::default();
2653		let (mut ext, _) = call_setup.ext();
2654
2655		let result;
2656		#[block]
2657		{
2658			result = run_builtin_precompile(
2659				&mut ext,
2660				H160::from_low_u64_be(4).as_fixed_bytes(),
2661				input.clone(),
2662			);
2663		}
2664		assert_eq!(input, result.unwrap().data);
2665	}
2666
2667	// `n`: Input to hash in bytes
2668	#[benchmark(pov_mode = Measured)]
2669	fn ripemd_160(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2670		use ripemd::Digest;
2671		let input = vec![0u8; n as usize];
2672		let mut call_setup = CallSetup::<T>::default();
2673		let (mut ext, _) = call_setup.ext();
2674
2675		let result;
2676		#[block]
2677		{
2678			result = run_builtin_precompile(
2679				&mut ext,
2680				H160::from_low_u64_be(3).as_fixed_bytes(),
2681				input.clone(),
2682			);
2683		}
2684		let mut expected = [0u8; 32];
2685		expected[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input));
2686
2687		assert_eq!(expected.to_vec(), result.unwrap().data);
2688	}
2689
2690	// `n`: Input to hash in bytes
2691	#[benchmark(pov_mode = Measured)]
2692	fn seal_hash_keccak_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2693		build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]);
2694
2695		let result;
2696		#[block]
2697		{
2698			result = runtime.bench_hash_keccak_256(memory.as_mut_slice(), 32, n, 0);
2699		}
2700		assert_eq!(sp_io::hashing::keccak_256(&memory[32..]), &memory[0..32]);
2701		assert_ok!(result);
2702	}
2703
2704	// `n`: Input to hash in bytes
2705	#[benchmark(pov_mode = Measured)]
2706	fn hash_blake2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2707		let input = vec![0u8; n as usize];
2708		let input_bytes = ISystem::ISystemCalls::hashBlake256(ISystem::hashBlake256Call {
2709			input: input.clone().into(),
2710		})
2711		.abi_encode();
2712
2713		let mut call_setup = CallSetup::<T>::default();
2714		let (mut ext, _) = call_setup.ext();
2715
2716		let result;
2717		#[block]
2718		{
2719			result = run_builtin_precompile(
2720				&mut ext,
2721				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
2722				input_bytes,
2723			);
2724		}
2725		let truth: [u8; 32] = sp_io::hashing::blake2_256(&input);
2726		let truth = FixedBytes::<32>::abi_encode(&truth);
2727		let truth = FixedBytes::<32>::abi_decode(&truth[..]).expect("decoding failed");
2728
2729		let raw_data = result.unwrap().data;
2730		let ret_hash = FixedBytes::<32>::abi_decode(&raw_data[..]).expect("decoding failed");
2731		assert_eq!(truth, ret_hash);
2732	}
2733
2734	// `n`: Input to hash in bytes
2735	#[benchmark(pov_mode = Measured)]
2736	fn hash_blake2_128(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2737		let input = vec![0u8; n as usize];
2738		let input_bytes = ISystem::ISystemCalls::hashBlake128(ISystem::hashBlake128Call {
2739			input: input.clone().into(),
2740		})
2741		.abi_encode();
2742
2743		let mut call_setup = CallSetup::<T>::default();
2744		let (mut ext, _) = call_setup.ext();
2745
2746		let result;
2747		#[block]
2748		{
2749			result = run_builtin_precompile(
2750				&mut ext,
2751				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
2752				input_bytes,
2753			);
2754		}
2755		let truth: [u8; 16] = sp_io::hashing::blake2_128(&input);
2756		let truth = FixedBytes::<16>::abi_encode(&truth);
2757		let truth = FixedBytes::<16>::abi_decode(&truth[..]).expect("decoding failed");
2758
2759		let raw_data = result.unwrap().data;
2760		let ret_hash = FixedBytes::<16>::abi_decode(&raw_data[..]).expect("decoding failed");
2761		assert_eq!(truth, ret_hash);
2762	}
2763
2764	// `n`: Message input length to verify in bytes.
2765	// need some buffer so the code size does not exceed the max code size.
2766	#[benchmark(pov_mode = Measured)]
2767	fn seal_sr25519_verify(n: Linear<0, { limits::code::BLOB_BYTES - 255 }>) {
2768		let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::<Vec<_>>();
2769		let message_len = message.len() as u32;
2770
2771		let key_type = sp_core::crypto::KeyTypeId(*b"code");
2772		let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
2773		let sig =
2774			sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
2775		let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec();
2776		let sig_len = sig.len() as u32;
2777
2778		build_runtime!(runtime, memory: [sig, pub_key.to_vec(), message, ]);
2779
2780		let result;
2781		#[block]
2782		{
2783			result = runtime.bench_sr25519_verify(
2784				memory.as_mut_slice(),
2785				0,                              // signature_ptr
2786				sig_len,                        // pub_key_ptr
2787				message_len,                    // message_len
2788				sig_len + pub_key.len() as u32, // message_ptr
2789			);
2790		}
2791
2792		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2793	}
2794
2795	#[benchmark(pov_mode = Measured)]
2796	fn ecdsa_recover() {
2797		use hex_literal::hex;
2798		let input = hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549").to_vec();
2799		let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b");
2800		let mut call_setup = CallSetup::<T>::default();
2801		let (mut ext, _) = call_setup.ext();
2802
2803		let result;
2804
2805		#[block]
2806		{
2807			result =
2808				run_builtin_precompile(&mut ext, H160::from_low_u64_be(1).as_fixed_bytes(), input);
2809		}
2810
2811		assert_eq!(result.unwrap().data, expected);
2812	}
2813
2814	#[benchmark(pov_mode = Measured)]
2815	fn p256_verify() {
2816		use hex_literal::hex;
2817		let input = hex!("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").to_vec();
2818		let expected = U256::one().to_big_endian();
2819		let mut call_setup = CallSetup::<T>::default();
2820		let (mut ext, _) = call_setup.ext();
2821
2822		let result;
2823
2824		#[block]
2825		{
2826			result = run_builtin_precompile(
2827				&mut ext,
2828				H160::from_low_u64_be(0x100).as_fixed_bytes(),
2829				input,
2830			);
2831		}
2832
2833		assert_eq!(result.unwrap().data, expected);
2834	}
2835
2836	#[benchmark(pov_mode = Measured)]
2837	fn bn128_add() {
2838		use hex_literal::hex;
2839		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b3625f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd5850b38c7ced6e4daef9c4347f370d6d8b58f4b1d8dc61a3c59d651a0644a2a27cf").to_vec();
2840		let expected = hex!(
2841			"0a6678fd675aa4d8f0d03a1feb921a27f38ebdcb860cc083653519655acd6d79172fd5b3b2bfdd44e43bcec3eace9347608f9f0a16f1e184cb3f52e6f259cbeb"
2842		);
2843		let mut call_setup = CallSetup::<T>::default();
2844		let (mut ext, _) = call_setup.ext();
2845
2846		let result;
2847		#[block]
2848		{
2849			result =
2850				run_builtin_precompile(&mut ext, H160::from_low_u64_be(6).as_fixed_bytes(), input);
2851		}
2852
2853		assert_eq!(result.unwrap().data, expected);
2854	}
2855
2856	#[benchmark(pov_mode = Measured)]
2857	fn bn128_mul() {
2858		use hex_literal::hex;
2859		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").to_vec();
2860		let expected = hex!(
2861			"0bf982b98a2757878c051bfe7eee228b12bc69274b918f08d9fcb21e9184ddc10b17c77cbf3c19d5d27e18cbd4a8c336afb488d0e92c18d56e64dd4ea5c437e6"
2862		);
2863		let mut call_setup = CallSetup::<T>::default();
2864		let (mut ext, _) = call_setup.ext();
2865
2866		let result;
2867		#[block]
2868		{
2869			result =
2870				run_builtin_precompile(&mut ext, H160::from_low_u64_be(7).as_fixed_bytes(), input);
2871		}
2872
2873		assert_eq!(result.unwrap().data, expected);
2874	}
2875
2876	// `n`: pairings to perform
2877	#[benchmark(pov_mode = Measured)]
2878	fn bn128_pairing(n: Linear<0, { 20 }>) {
2879		fn generate_random_ecpairs(n: usize) -> Vec<u8> {
2880			use bn::{AffineG1, AffineG2, Fr, G1, G2, Group};
2881			use rand::SeedableRng;
2882			use rand_pcg::Pcg64;
2883			let mut rng = Pcg64::seed_from_u64(1);
2884
2885			let mut buffer = vec![0u8; n * 192];
2886
2887			let mut write = |element: &bn::Fq, offset: &mut usize| {
2888				element.to_big_endian(&mut buffer[*offset..*offset + 32]).unwrap();
2889				*offset += 32
2890			};
2891
2892			for i in 0..n {
2893				let mut offset = i * 192;
2894				let scalar = Fr::random(&mut rng);
2895
2896				let g1 = G1::one() * scalar;
2897				let g2 = G2::one() * scalar;
2898				let a = AffineG1::from_jacobian(g1).expect("G1 point should be on curve");
2899				let b = AffineG2::from_jacobian(g2).expect("G2 point should be on curve");
2900
2901				write(&a.x(), &mut offset);
2902				write(&a.y(), &mut offset);
2903				write(&b.x().imaginary(), &mut offset);
2904				write(&b.x().real(), &mut offset);
2905				write(&b.y().imaginary(), &mut offset);
2906				write(&b.y().real(), &mut offset);
2907			}
2908
2909			buffer
2910		}
2911
2912		let input = generate_random_ecpairs(n as usize);
2913		let mut call_setup = CallSetup::<T>::default();
2914		let (mut ext, _) = call_setup.ext();
2915
2916		let result;
2917		#[block]
2918		{
2919			result =
2920				run_builtin_precompile(&mut ext, H160::from_low_u64_be(8).as_fixed_bytes(), input);
2921		}
2922		assert_ok!(result);
2923	}
2924
2925	// `n`: number of rounds to perform
2926	#[benchmark(pov_mode = Measured)]
2927	fn blake2f(n: Linear<0, 1200>) {
2928		use hex_literal::hex;
2929		let input = hex!(
2930			"48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"
2931		);
2932		let input = n.to_be_bytes().to_vec().into_iter().chain(input.to_vec()).collect::<Vec<_>>();
2933		let mut call_setup = CallSetup::<T>::default();
2934		let (mut ext, _) = call_setup.ext();
2935
2936		let result;
2937		#[block]
2938		{
2939			result =
2940				run_builtin_precompile(&mut ext, H160::from_low_u64_be(9).as_fixed_bytes(), input);
2941		}
2942		assert_ok!(result);
2943	}
2944
2945	// Only calling the function itself for the list of
2946	// generated different ECDSA keys.
2947	// This is a slow call: We reduce the number of runs.
2948	#[benchmark(pov_mode = Measured)]
2949	fn seal_ecdsa_to_eth_address() {
2950		let key_type = sp_core::crypto::KeyTypeId(*b"code");
2951		let pub_key_bytes = sp_io::crypto::ecdsa_generate(key_type, None).0;
2952		build_runtime!(runtime, memory: [[0u8; 20], pub_key_bytes,]);
2953
2954		let result;
2955		#[block]
2956		{
2957			result = runtime.bench_ecdsa_to_eth_address(
2958				memory.as_mut_slice(),
2959				20, // key_ptr
2960				0,  // output_ptr
2961			);
2962		}
2963
2964		assert_ok!(result);
2965		assert_eq!(&memory[..20], runtime.ext().ecdsa_to_eth_address(&pub_key_bytes).unwrap());
2966	}
2967
2968	/// Benchmark the cost of executing `r` noop (JUMPDEST) instructions.
2969	#[benchmark(pov_mode = Measured)]
2970	fn evm_opcode(r: Linear<0, 10_000>) -> Result<(), BenchmarkError> {
2971		let module = VmBinaryModule::evm_noop(r);
2972		let inputs = vec![];
2973
2974		let code = Bytecode::new_raw(revm::primitives::Bytes::from(module.code.clone()));
2975		let mut setup = CallSetup::<T>::new(module);
2976		let (mut ext, _) = setup.ext();
2977
2978		let result;
2979		#[block]
2980		{
2981			result = evm::call(code, &mut ext, inputs);
2982		}
2983
2984		assert!(result.is_ok());
2985		Ok(())
2986	}
2987
2988	// Benchmark the execution of instructions.
2989	//
2990	// It benchmarks the absolute worst case by allocating a lot of memory
2991	// and then accessing it so that each instruction generates two cache misses.
2992	#[benchmark(pov_mode = Ignored)]
2993	fn instr(r: Linear<0, 10_000>) {
2994		use rand::{SeedableRng, seq::SliceRandom};
2995		use rand_pcg::Pcg64;
2996
2997		// Ideally, this needs to be bigger than the cache.
2998		const MEMORY_SIZE: u64 = sp_core::MAX_POSSIBLE_ALLOCATION as u64;
2999
3000		// This is benchmarked for x86-64.
3001		const CACHE_LINE_SIZE: u64 = 64;
3002
3003		// An 8 byte load from this misalignment will reach into the subsequent line.
3004		const MISALIGNMENT: u64 = 60;
3005
3006		// We only need one address per cache line.
3007		// -1 because we skip the first address
3008		const NUM_ADDRESSES: u64 = (MEMORY_SIZE - MISALIGNMENT) / CACHE_LINE_SIZE - 1;
3009
3010		assert!(
3011			u64::from(r) <= NUM_ADDRESSES / 2,
3012			"If we do too many iterations we run into the risk of loading from warm cache lines",
3013		);
3014
3015		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(true));
3016		let (mut ext, module) = setup.ext();
3017		let mut prepared =
3018			CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), MEMORY_SIZE as u32);
3019
3020		assert!(
3021			u64::from(prepared.aux_data_base()) & (CACHE_LINE_SIZE - 1) == 0,
3022			"aux data base must be cache aligned"
3023		);
3024
3025		// Addresses data will be located inside the aux data.
3026		let misaligned_base = u64::from(prepared.aux_data_base()) + MISALIGNMENT;
3027
3028		// Create all possible addresses and shuffle them. This makes sure
3029		// the accesses are random but no address is accessed more than once.
3030		// we skip the first address since it is our entry point
3031		let mut addresses = Vec::with_capacity(NUM_ADDRESSES as usize);
3032		for i in 1..NUM_ADDRESSES {
3033			let addr = (misaligned_base + i * CACHE_LINE_SIZE).to_le_bytes();
3034			addresses.push(addr);
3035		}
3036		let mut rng = Pcg64::seed_from_u64(1337);
3037		addresses.shuffle(&mut rng);
3038
3039		// The addresses need to be padded to be one cache line apart.
3040		let mut memory = Vec::with_capacity((NUM_ADDRESSES * CACHE_LINE_SIZE) as usize);
3041		for address in addresses {
3042			memory.extend_from_slice(&address);
3043			memory.resize(memory.len() + CACHE_LINE_SIZE as usize - address.len(), 0);
3044		}
3045
3046		// Copies `memory` to `aux_data_base + MISALIGNMENT`.
3047		// Sets `a0 = MISALIGNMENT` and `a1 = r`.
3048		prepared
3049			.setup_aux_data(memory.as_slice(), MISALIGNMENT as u32, r.into())
3050			.unwrap();
3051
3052		#[block]
3053		{
3054			prepared.call().unwrap();
3055		}
3056	}
3057
3058	#[benchmark(pov_mode = Ignored)]
3059	fn instr_empty_loop(r: Linear<0, 10_000>) {
3060		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(false));
3061		let (mut ext, module) = setup.ext();
3062		let mut prepared = CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), 0);
3063		prepared.setup_aux_data(&[], 0, r.into()).unwrap();
3064
3065		#[block]
3066		{
3067			prepared.call().unwrap();
3068		}
3069	}
3070
3071	#[benchmark(pov_mode = Measured)]
3072	fn extcodecopy(n: Linear<1_000, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
3073		// The caller contract; `CallSetup` whitelists its `AccountInfoOf`.
3074		let mut setup = CallSetup::<T>::new(VmBinaryModule::dummy());
3075		// Copy a contract other than the caller, so its `AccountInfoOf` read is counted.
3076		let target = Contract::<T>::with_index(1, VmBinaryModule::sized(n), vec![])?;
3077
3078		let (mut ext, _) = setup.ext();
3079		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
3080
3081		// Setup stack for extcodecopy instruction: [address, dest_offset, offset, size]
3082		let _ = interpreter.stack.push(U256::from(n));
3083		let _ = interpreter.stack.push(U256::from(0u32));
3084		let _ = interpreter.stack.push(U256::from(0u32));
3085		let _ = interpreter.stack.push(target.address);
3086
3087		let result;
3088		#[block]
3089		{
3090			result = instructions::host::extcodecopy(&mut interpreter);
3091		}
3092
3093		assert!(result.is_continue());
3094		assert_eq!(
3095			*interpreter.memory.slice(0..n as usize),
3096			PristineCode::<T>::get(target.info()?.code_hash).unwrap()[0..n as usize],
3097			"Memory should contain the target contract's code after extcodecopy"
3098		);
3099
3100		Ok(())
3101	}
3102
3103	#[benchmark]
3104	fn v1_migration_step() {
3105		use crate::migrations::v1;
3106		let addr = H160::from([1u8; 20]);
3107		let contract_info = ContractInfo::new(&addr, 1u32.into(), Default::default()).unwrap();
3108
3109		v1::old::ContractInfoOf::<T>::insert(addr, contract_info.clone());
3110		let mut meter = WeightMeter::new();
3111		assert_eq!(AccountInfo::<T>::load_contract(&addr), None);
3112
3113		#[block]
3114		{
3115			v1::Migration::<T>::step(None, &mut meter).unwrap();
3116		}
3117
3118		assert_eq!(v1::old::ContractInfoOf::<T>::get(&addr), None);
3119		assert_eq!(AccountInfo::<T>::load_contract(&addr).unwrap(), contract_info);
3120
3121		// uses twice the weight once for migration and then for checking if there is another key.
3122		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v1_migration_step() * 2);
3123	}
3124
3125	#[benchmark]
3126	fn v2_migration_step() {
3127		use crate::migrations::v2;
3128		let code_hash = H256::from([0; 32]);
3129		let old_code_info = v2::Migration::<T>::create_old_code_info(
3130			whitelisted_caller(),
3131			1000u32.into(),
3132			1,
3133			100,
3134			0,
3135		);
3136		v2::Migration::<T>::insert_old_code_info(code_hash, old_code_info.clone());
3137		let mut meter = WeightMeter::new();
3138
3139		#[block]
3140		{
3141			v2::Migration::<T>::step(None, &mut meter).unwrap();
3142		}
3143
3144		v2::Migration::<T>::assert_migrated_code_info(code_hash, &old_code_info);
3145
3146		// uses twice the weight once for migration and then for checking if there is another key.
3147		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v2_migration_step() * 2);
3148	}
3149
3150	#[benchmark]
3151	fn v3_migration_step() {
3152		use crate::migrations::v3;
3153		// Remove all pre-existing accounts
3154		let _ = frame_system::Account::<T>::clear(u32::MAX, None);
3155
3156		let account = account::<T::AccountId>("target", 0, 0);
3157		T::Currency::mint_into(&account, Pallet::<T>::min_balance())
3158			.expect("should mint into account");
3159
3160		// clear the mapping so the migration has work to do
3161		let addr = T::AddressMapper::to_address(&account);
3162		crate::OriginalAccount::<T>::remove(addr);
3163
3164		assert!(!T::AddressMapper::is_mapped(&account));
3165		let mut meter = WeightMeter::new();
3166
3167		#[block]
3168		{
3169			v3::Migration::<T>::step(None, &mut meter).unwrap();
3170		}
3171
3172		assert!(T::AddressMapper::is_mapped(&account));
3173
3174		// uses twice the weight: once for migration and then for checking if there is another key.
3175		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v3_migration_step() * 2);
3176	}
3177
3178	/// One iteration of v4 phase 1: credit the uploader's [`NativeDepositOf`] bucket.
3179	///
3180	/// Seeds two codes and primes the cursor with the first stored entry so the benched
3181	/// iteration exercises the `iter_from` path that dominates phase 1 in production.
3182	#[benchmark]
3183	fn v4_code_upload_step() {
3184		use crate::migrations::v4;
3185
3186		let _ = CodeInfoOf::<T>::clear(u32::MAX, None);
3187
3188		let owner: T::AccountId = whitelisted_caller();
3189		let deposit: BalanceOf<T> = 1_000u32.into();
3190
3191		let pallet_account = Pallet::<T>::account_id();
3192		T::Currency::mint_into(&pallet_account, Pallet::<T>::min_balance()).unwrap();
3193		T::Currency::mint_into(&pallet_account, deposit).unwrap();
3194		T::Currency::hold(&HoldReason::CodeUploadDepositReserve.into(), &pallet_account, deposit)
3195			.unwrap();
3196
3197		CodeInfoOf::<T>::insert(
3198			H256::from([1u8; 32]),
3199			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
3200		);
3201		CodeInfoOf::<T>::insert(
3202			H256::from([2u8; 32]),
3203			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
3204		);
3205
3206		let first = match v4::Migration::<T>::step_once(None) {
3207			Some(v4::Cursor::CodeUpload(h)) => h,
3208			other => panic!("expected CodeUpload cursor, got {other:?}"),
3209		};
3210		let cursor = Some(v4::Cursor::CodeUpload(first));
3211
3212		#[block]
3213		{
3214			let _ = v4::Migration::<T>::step_once(cursor);
3215		}
3216
3217		assert_eq!(
3218			NativeDepositOf::<T>::get(&pallet_account, &owner),
3219			deposit + deposit,
3220			"both code uploads credited to owner",
3221		);
3222	}
3223
3224	/// One iteration of v4 phase 2: burn native hold, mint and hold PGAS for a single contract.
3225	///
3226	/// Seeds two contracts and primes the cursor with the first stored entry so the benched
3227	/// iteration exercises the `iter_from` path that dominates phase 2 in production.
3228	#[benchmark]
3229	fn v4_contract_step() {
3230		use crate::migrations::v4;
3231
3232		let _ = AccountInfoOf::<T>::clear(u32::MAX, None);
3233
3234		let code_hash = H256::from([0u8; 32]);
3235		let deposit: BalanceOf<T> = 1_000u32.into();
3236
3237		for byte in [0x41u8, 0x42u8] {
3238			let addr = H160::from([byte; 20]);
3239			let contract_account = T::AddressMapper::to_account_id(&addr);
3240			let info =
3241				ContractInfo::<T>::new(&addr, 1u32.into(), code_hash).expect("fresh contract info");
3242			AccountInfoOf::<T>::insert(
3243				addr,
3244				crate::storage::AccountInfo::<T> {
3245					account_type: crate::storage::AccountType::Contract(info),
3246					dust: 0,
3247				},
3248			);
3249			T::Currency::mint_into(&contract_account, Pallet::<T>::min_balance()).unwrap();
3250			T::Currency::mint_into(&contract_account, deposit).unwrap();
3251			T::Currency::hold(
3252				&HoldReason::StorageDepositReserve.into(),
3253				&contract_account,
3254				deposit,
3255			)
3256			.unwrap();
3257		}
3258
3259		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::Contract(None))) {
3260			Some(v4::Cursor::Contract(Some(addr))) => addr,
3261			other => panic!("expected Contract cursor, got {other:?}"),
3262		};
3263		let cursor = Some(v4::Cursor::Contract(Some(first)));
3264
3265		#[block]
3266		{
3267			let _ = v4::Migration::<T>::step_once(cursor);
3268		}
3269
3270		// `migrate_native_to_pgas` is a no-op for `Deposit = ()`, so the hold only clears on
3271		// PGAS-backed runtimes. On non-PGAS runtimes the benchmark still measures the iter cost.
3272		if T::Deposit::SUPPORTS_PGAS {
3273			for byte in [0x41u8, 0x42u8] {
3274				let addr = H160::from([byte; 20]);
3275				let contract_account = T::AddressMapper::to_account_id(&addr);
3276				assert_eq!(
3277					T::Currency::balance_on_hold(
3278						&HoldReason::StorageDepositReserve.into(),
3279						&contract_account,
3280					),
3281					0u32.into(),
3282					"native storage deposit burned for {addr:?}",
3283				);
3284			}
3285		}
3286	}
3287
3288	/// One iteration of v4 phase 3: rewrite a legacy [`v4::old::DeletionQueue`] entry into the
3289	/// new [`DeletionQueue`] format.
3290	///
3291	/// Seeds two legacy entries and primes the cursor with the first stored entry so the benched
3292	/// iteration exercises the `iter_from` path.
3293	#[benchmark]
3294	fn v4_deletion_queue_step() {
3295		use crate::migrations::v4;
3296
3297		let _ = v4::old::DeletionQueue::<T>::clear(u32::MAX, None);
3298
3299		let trie_a: TrieId = vec![0xAAu8; 16].try_into().unwrap();
3300		let trie_b: TrieId = vec![0xBBu8; 24].try_into().unwrap();
3301		v4::old::DeletionQueue::<T>::insert(0u32, trie_a);
3302		v4::old::DeletionQueue::<T>::insert(1u32, trie_b);
3303
3304		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::DeletionQueue(None))) {
3305			Some(v4::Cursor::DeletionQueue(Some(key))) => key,
3306			other => panic!("expected DeletionQueue cursor, got {other:?}"),
3307		};
3308		let cursor = Some(v4::Cursor::DeletionQueue(Some(first)));
3309
3310		#[block]
3311		{
3312			let _ = v4::Migration::<T>::step_once(cursor);
3313		}
3314
3315		assert!(
3316			DeletionQueue::<T>::get(0u32).is_some() && DeletionQueue::<T>::get(1u32).is_some(),
3317			"both legacy entries rewritten into the new format",
3318		);
3319	}
3320
3321	/// Helper function to create a test signer for finalize_block benchmark
3322	fn create_test_signer<T: Config>() -> (T::AccountId, SigningKey, H160) {
3323		use hex_literal::hex;
3324		// dev::alith()
3325		let signer_account_id = hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac");
3326		let signer_priv_key =
3327			hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133");
3328
3329		let signer_key = SigningKey::from_bytes(&signer_priv_key.into()).expect("valid key");
3330
3331		let signer_address = H160::from_slice(&signer_account_id);
3332		let signer_caller = T::AddressMapper::to_fallback_account_id(&signer_address);
3333
3334		(signer_caller, signer_key, signer_address)
3335	}
3336
3337	/// Helper function to create and sign a transaction for finalize_block benchmark
3338	fn create_signed_transaction<T: Config>(
3339		signer_key: &SigningKey,
3340		target_address: H160,
3341		value: U256,
3342		input_data: Vec<u8>,
3343	) -> Vec<u8> {
3344		let unsigned_tx: TransactionUnsigned = TransactionLegacyUnsigned {
3345			to: Some(target_address),
3346			value,
3347			chain_id: Some(T::ChainId::get().into()),
3348			input: input_data.into(),
3349			..Default::default()
3350		}
3351		.into();
3352
3353		let hashed_payload = sp_io::hashing::keccak_256(&unsigned_tx.unsigned_payload());
3354		let (signature, recovery_id) =
3355			signer_key.sign_prehash_recoverable(&hashed_payload).expect("signing success");
3356
3357		let mut sig_bytes = [0u8; 65];
3358		sig_bytes[..64].copy_from_slice(&signature.to_bytes());
3359		sig_bytes[64] = recovery_id.to_byte();
3360
3361		let signed_tx = unsigned_tx.with_signature(sig_bytes);
3362
3363		signed_tx.signed_payload()
3364	}
3365
3366	/// Helper function to generate common finalize_block benchmark setup
3367	fn setup_finalize_block_benchmark<T>()
3368	-> Result<(Contract<T>, BalanceOf<T>, U256, SigningKey, BlockNumberFor<T>), BenchmarkError>
3369	where
3370		BalanceOf<T>: Into<U256> + TryFrom<U256>,
3371		T: Config,
3372		MomentOf<T>: Into<U256>,
3373		<T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
3374	{
3375		// Setup test signer
3376		let (signer_caller, signer_key, _signer_address) = create_test_signer::<T>();
3377		whitelist_account!(signer_caller);
3378
3379		// Setup contract instance
3380		let instance =
3381			Contract::<T>::with_caller(signer_caller.clone(), VmBinaryModule::dummy(), vec![])?;
3382		let storage_deposit = default_deposit_limit::<T>();
3383		let value = Pallet::<T>::min_balance();
3384		let evm_value =
3385			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, 0));
3386
3387		// Setup block
3388		let current_block = BlockNumberFor::<T>::from(1u32);
3389		frame_system::Pallet::<T>::set_block_number(current_block);
3390
3391		Ok((instance, storage_deposit, evm_value, signer_key, current_block))
3392	}
3393
3394	/// Benchmark the `on_finalize` hook scaling with number of transactions.
3395	///
3396	/// This benchmark measures the marginal computational cost of adding transactions
3397	/// to a block during finalization, with fixed payload size to isolate transaction
3398	/// count scaling effects.
3399	///
3400	/// ## Parameters:
3401	/// - `n`: Number of transactions in the block (0-200)
3402	///
3403	/// ## Test Setup:
3404	/// - Creates `n` transactions with fixed 100-byte payloads
3405	/// - Pre-populates block builder storage with test data
3406	///
3407	/// ## Usage:
3408	/// Use this with `on_finalize_per_byte` to calculate total cost:
3409	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
3410	#[benchmark(pov_mode = Measured)]
3411	fn on_finalize_per_transaction(n: Linear<0, 200>) -> Result<(), BenchmarkError> {
3412		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3413			setup_finalize_block_benchmark::<T>()?;
3414
3415		// Fixed payload size to isolate transaction count effects
3416		let fixed_payload_size = 100usize;
3417
3418		// Pre-populate InflightTransactions with n transactions of fixed size
3419		if n > 0 {
3420			// Initialize block
3421			let _ = Pallet::<T>::on_initialize(current_block);
3422
3423			// Create input data of fixed size for consistent transaction payloads
3424			let input_data = vec![0x42u8; fixed_payload_size];
3425			let receipt_gas_info = ReceiptGasInfo {
3426				gas_used: U256::from(1_000_000),
3427				effective_gas_price: Pallet::<T>::evm_base_fee(),
3428			};
3429
3430			for _ in 0..n {
3431				// Create real signed transaction with fixed-size input data
3432				let signed_transaction = create_signed_transaction::<T>(
3433					&signer_key,
3434					instance.address,
3435					evm_value,
3436					input_data.clone(),
3437				);
3438
3439				// Store transaction
3440				let _ = block_storage::bench_with_ethereum_context(|| {
3441					let (encoded_logs, bloom) =
3442						block_storage::get_receipt_details().unwrap_or_default();
3443
3444					let block_builder_ir = EthBlockBuilderIR::<T>::get();
3445					let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3446
3447					block_builder.process_transaction(
3448						signed_transaction,
3449						true,
3450						receipt_gas_info.clone(),
3451						encoded_logs,
3452						bloom,
3453					);
3454
3455					EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3456				});
3457			}
3458		}
3459
3460		#[block]
3461		{
3462			// Measure only the finalization cost with n transactions of fixed size
3463			let _ = Pallet::<T>::on_finalize(current_block);
3464		}
3465
3466		// Verify transaction count
3467		assert_eq!(Pallet::<T>::eth_block().transactions.len(), n as usize);
3468
3469		Ok(())
3470	}
3471
3472	/// Benchmark the `on_finalize` hook scaling with transaction payload size.
3473	///
3474	/// This benchmark measures the marginal computational cost of processing
3475	/// larger transaction payloads during finalization, with fixed transaction count
3476	/// to isolate payload size scaling effects.
3477	///
3478	/// ## Parameters:
3479	/// - `d`: Payload size per transaction in bytes (0-1000)
3480	///
3481	/// ## Test Setup:
3482	/// - Creates 10 transactions with payload size `d`
3483	/// - Pre-populates block builder storage with test data
3484	///
3485	/// ## Usage:
3486	/// Use this with `on_finalize_per_transaction` to calculate total cost:
3487	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
3488	#[benchmark(pov_mode = Measured)]
3489	fn on_finalize_per_transaction_data(d: Linear<0, 1000>) -> Result<(), BenchmarkError> {
3490		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3491			setup_finalize_block_benchmark::<T>()?;
3492
3493		// Fixed transaction count to isolate payload size effects
3494		let fixed_tx_count = 10u32;
3495
3496		// Initialize block
3497		let _ = Pallet::<T>::on_initialize(current_block);
3498
3499		// Create input data of variable size p for realistic transaction payloads
3500		let input_data = vec![0x42u8; d as usize];
3501		let receipt_gas_info = ReceiptGasInfo {
3502			gas_used: U256::from(1_000_000),
3503			effective_gas_price: Pallet::<T>::evm_base_fee(),
3504		};
3505
3506		for _ in 0..fixed_tx_count {
3507			// Create real signed transaction with variable-size input data
3508			let signed_transaction = create_signed_transaction::<T>(
3509				&signer_key,
3510				instance.address,
3511				evm_value,
3512				input_data.clone(),
3513			);
3514
3515			// Store transaction
3516			let _ = block_storage::bench_with_ethereum_context(|| {
3517				let (encoded_logs, bloom) =
3518					block_storage::get_receipt_details().unwrap_or_default();
3519
3520				let block_builder_ir = EthBlockBuilderIR::<T>::get();
3521				let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3522
3523				block_builder.process_transaction(
3524					signed_transaction,
3525					true,
3526					receipt_gas_info.clone(),
3527					encoded_logs,
3528					bloom,
3529				);
3530
3531				EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3532			});
3533		}
3534
3535		#[block]
3536		{
3537			// Measure only the finalization cost with fixed count, variable payload size
3538			let _ = Pallet::<T>::on_finalize(current_block);
3539		}
3540
3541		// Verify transaction count
3542		assert_eq!(Pallet::<T>::eth_block().transactions.len(), fixed_tx_count as usize);
3543
3544		Ok(())
3545	}
3546
3547	/// Benchmark the `on_finalize` per-event costs.
3548	///
3549	/// This benchmark measures the computational cost of processing events
3550	/// within the finalization process, isolating the overhead of event count.
3551	/// Uses a single transaction with varying numbers of minimal events.
3552	///
3553	/// ## Parameters:
3554	/// - `e`: Number of events per transaction
3555	///
3556	/// ## Test Setup:
3557	/// - Creates 1 transaction with `e` ContractEmitted events
3558	/// - Each event contains minimal data (no topics, empty data field)
3559	///
3560	/// ## Usage:
3561	/// Measures the per-event processing overhead during finalization
3562	/// - Fixed cost: `on_finalize_per_event(0)` - baseline finalization cost
3563	/// - Per event: `on_finalize_per_event(e)` - linear scaling with event count
3564	#[benchmark(pov_mode = Measured)]
3565	fn on_finalize_per_event(e: Linear<0, 100>) -> Result<(), BenchmarkError> {
3566		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3567			setup_finalize_block_benchmark::<T>()?;
3568
3569		// Create a single transaction with e events, each with minimal data
3570		let input_data = vec![0x42u8; 100];
3571		let signed_transaction = create_signed_transaction::<T>(
3572			&signer_key,
3573			instance.address,
3574			evm_value,
3575			input_data.clone(),
3576		);
3577
3578		let receipt_gas_info = ReceiptGasInfo {
3579			gas_used: U256::from(1_000_000),
3580			effective_gas_price: Pallet::<T>::evm_base_fee(),
3581		};
3582
3583		// Store transaction
3584		let _ = block_storage::bench_with_ethereum_context(|| {
3585			let (encoded_logs, bloom) = block_storage::get_receipt_details().unwrap_or_default();
3586
3587			let block_builder_ir = EthBlockBuilderIR::<T>::get();
3588			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3589
3590			block_builder.process_transaction(
3591				signed_transaction,
3592				true,
3593				receipt_gas_info.clone(),
3594				encoded_logs,
3595				bloom,
3596			);
3597
3598			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3599		});
3600
3601		// Create e events with minimal data to isolate event count overhead
3602		for _ in 0..e {
3603			block_storage::capture_ethereum_log(&instance.address, &vec![], &vec![]);
3604		}
3605
3606		#[block]
3607		{
3608			// Initialize block
3609			let _ = Pallet::<T>::on_initialize(current_block);
3610
3611			// Measure the finalization cost with e events
3612			let _ = Pallet::<T>::on_finalize(current_block);
3613		}
3614
3615		// Verify transaction count
3616		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
3617
3618		Ok(())
3619	}
3620
3621	/// ## Test Setup:
3622	/// - Creates 1 transaction with 1 ContractEmitted event
3623	/// - Event contains `d` total bytes of data across data field and topics
3624	///
3625	/// ## Usage:
3626	/// Measures the per-byte event data processing overhead during finalization
3627	/// - Fixed cost: `on_finalize_per_event_data(0)` - baseline cost with empty event
3628	/// - Per byte: `on_finalize_per_event_data(d)` - linear scaling with data size
3629	#[benchmark(pov_mode = Measured)]
3630	fn on_finalize_per_event_data(d: Linear<0, 16384>) -> Result<(), BenchmarkError> {
3631		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3632			setup_finalize_block_benchmark::<T>()?;
3633
3634		// Create a single transaction with one event containing d bytes of data
3635		let input_data = vec![0x42u8; 100];
3636		let signed_transaction = create_signed_transaction::<T>(
3637			&signer_key,
3638			instance.address,
3639			evm_value,
3640			input_data.clone(),
3641		);
3642
3643		let receipt_gas_info = ReceiptGasInfo {
3644			gas_used: U256::from(1_000_000),
3645			effective_gas_price: Pallet::<T>::evm_base_fee(),
3646		};
3647
3648		// Store transaction
3649		let _ = block_storage::bench_with_ethereum_context(|| {
3650			let (encoded_logs, bloom) = block_storage::get_receipt_details().unwrap_or_default();
3651
3652			let block_builder_ir = EthBlockBuilderIR::<T>::get();
3653			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3654
3655			block_builder.process_transaction(
3656				signed_transaction,
3657				true,
3658				receipt_gas_info,
3659				encoded_logs,
3660				bloom,
3661			);
3662
3663			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3664		});
3665
3666		// Create one event with d bytes of data distributed across topics and data field
3667		let (event_data, topics) = if d < 32 {
3668			// If total data is less than 32 bytes, put all in data field
3669			(vec![0x42u8; d as usize], vec![])
3670		} else {
3671			// Fill topics first, then put remaining bytes in data field
3672			let num_topics = core::cmp::min(limits::NUM_EVENT_TOPICS, d / 32);
3673			let topic_bytes_used = num_topics * 32;
3674			let data_bytes_remaining = d - topic_bytes_used;
3675
3676			// Create topics filled with sequential data
3677			let mut topics = Vec::new();
3678			for topic_index in 0..num_topics {
3679				let topic_data = [topic_index as u8; 32];
3680				topics.push(H256::from(topic_data));
3681			}
3682
3683			// Remaining bytes go to data field
3684			let event_data = vec![0x42u8; data_bytes_remaining as usize];
3685
3686			(event_data, topics)
3687		};
3688
3689		block_storage::capture_ethereum_log(&instance.address, &event_data, &topics);
3690
3691		#[block]
3692		{
3693			// Initialize block
3694			let _ = Pallet::<T>::on_initialize(current_block);
3695
3696			// Measure the finalization cost with d bytes of event data
3697			let _ = Pallet::<T>::on_finalize(current_block);
3698		}
3699
3700		// Verify transaction count
3701		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
3702
3703		Ok(())
3704	}
3705
3706	impl_benchmark_test_suite!(
3707		Contracts,
3708		crate::tests::ExtBuilder::default().build(),
3709		crate::tests::Test,
3710	);
3711}