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	// n: new byte size
1572	// o: old byte size
1573	#[benchmark(skip_meta, pov_mode = Measured)]
1574	fn seal_set_storage(
1575		n: Linear<0, { limits::STORAGE_BYTES }>,
1576		o: Linear<0, { limits::STORAGE_BYTES }>,
1577	) -> Result<(), BenchmarkError> {
1578		let max_key_len = limits::STORAGE_KEY_BYTES;
1579		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1580			.map_err(|_| "Key has wrong length")?;
1581		let value = vec![1u8; n as usize];
1582
1583		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
1584		let info = instance.info()?;
1585
1586		info.write(&key, Some(vec![42u8; o as usize]), None, false)
1587			.map_err(|_| "Failed to write to storage during setup.")?;
1588
1589		let result;
1590		#[block]
1591		{
1592			result = runtime.bench_set_storage(
1593				memory.as_mut_slice(),
1594				StorageFlags::empty().bits(),
1595				0,           // key_ptr
1596				max_key_len, // key_len
1597				max_key_len, // value_ptr
1598				n,           // value_len
1599			);
1600		}
1601
1602		assert_ok!(result);
1603		assert_eq!(info.read(&key).unwrap(), value);
1604		Ok(())
1605	}
1606
1607	#[benchmark(skip_meta, pov_mode = Measured)]
1608	fn seal_set_storage_hot(
1609		n: Linear<0, { limits::STORAGE_BYTES }>,
1610		o: Linear<0, { limits::STORAGE_BYTES }>,
1611	) -> Result<(), BenchmarkError> {
1612		let max_key_len = limits::STORAGE_KEY_BYTES;
1613		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1614			.map_err(|_| "Key has wrong length")?;
1615		let value = vec![1u8; n as usize];
1616
1617		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
1618		let info = instance.info()?;
1619
1620		info.write(&key, Some(vec![42u8; o as usize]), None, false)
1621			.map_err(|_| "Failed to write to storage during setup.")?;
1622
1623		frame_benchmarking::add_to_whitelist_child(
1624			info.child_trie_info().storage_key().to_vec(),
1625			key.hash(),
1626		);
1627
1628		// Add the key to access list so the op's touch is hot.
1629		runtime.ext().touch_storage_access(false, &key);
1630
1631		let result;
1632		#[block]
1633		{
1634			result = runtime.bench_set_storage(
1635				memory.as_mut_slice(),
1636				StorageFlags::empty().bits(),
1637				0,           // key_ptr
1638				max_key_len, // key_len
1639				max_key_len, // value_ptr
1640				n,           // value_len
1641			);
1642		}
1643
1644		assert_ok!(result);
1645		assert_eq!(info.read(&key).unwrap(), value);
1646		Ok(())
1647	}
1648
1649	#[benchmark(skip_meta, pov_mode = Measured)]
1650	fn clear_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1651		let key_byte = 0;
1652		let (mut call_setup, key, input_bytes) =
1653			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Cold)?;
1654		let (mut ext, _) = call_setup.ext();
1655		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1656			.map_err(|_| "Failed to write to storage during setup.")?;
1657
1658		let result;
1659		#[block]
1660		{
1661			result = run_builtin_precompile(
1662				&mut ext,
1663				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1664				input_bytes,
1665			);
1666		}
1667		assert_ok!(result);
1668		assert!(ext.get_storage(&key).is_none());
1669
1670		Ok(())
1671	}
1672
1673	#[benchmark(skip_meta, pov_mode = Measured)]
1674	fn clear_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1675		let key_byte = 0;
1676		let (mut call_setup, key, input_bytes) =
1677			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Hot)?;
1678		let (mut ext, _) = call_setup.ext();
1679		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1680			.map_err(|_| "Failed to write to storage during setup.")?;
1681
1682		ext.touch_storage_access(false, &key);
1683
1684		let result;
1685		#[block]
1686		{
1687			result = run_builtin_precompile(
1688				&mut ext,
1689				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1690				input_bytes,
1691			);
1692		}
1693		assert_ok!(result);
1694		assert!(ext.get_storage(&key).is_none());
1695
1696		Ok(())
1697	}
1698
1699	#[benchmark(skip_meta, pov_mode = Measured)]
1700	fn seal_get_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1701		let max_key_len = limits::STORAGE_KEY_BYTES;
1702		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1703			.map_err(|_| "Key has wrong length")?;
1704		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
1705		let info = instance.info()?;
1706
1707		info.write(&key, Some(vec![42u8; n as usize]), None, false)
1708			.map_err(|_| "Failed to write to storage during setup.")?;
1709
1710		let out_ptr = max_key_len + 4;
1711		let result;
1712		#[block]
1713		{
1714			result = runtime.bench_get_storage(
1715				memory.as_mut_slice(),
1716				StorageFlags::empty().bits(),
1717				0,           // key_ptr
1718				max_key_len, // key_len
1719				out_ptr,     // out_ptr
1720				max_key_len, // out_len_ptr
1721			);
1722		}
1723
1724		assert_ok!(result);
1725		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
1726		Ok(())
1727	}
1728
1729	#[benchmark(skip_meta, pov_mode = Measured)]
1730	fn seal_get_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1731		let max_key_len = limits::STORAGE_KEY_BYTES;
1732		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1733			.map_err(|_| "Key has wrong length")?;
1734		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
1735		let info = instance.info()?;
1736
1737		info.write(&key, Some(vec![42u8; n as usize]), None, false)
1738			.map_err(|_| "Failed to write to storage during setup.")?;
1739
1740		frame_benchmarking::add_to_whitelist_child(
1741			info.child_trie_info().storage_key().to_vec(),
1742			key.hash(),
1743		);
1744
1745		runtime.ext().touch_storage_access(false, &key);
1746
1747		let out_ptr = max_key_len + 4;
1748		let result;
1749		#[block]
1750		{
1751			result = runtime.bench_get_storage(
1752				memory.as_mut_slice(),
1753				StorageFlags::empty().bits(),
1754				0,           // key_ptr
1755				max_key_len, // key_len
1756				out_ptr,     // out_ptr
1757				max_key_len, // out_len_ptr
1758			);
1759		}
1760
1761		assert_ok!(result);
1762		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
1763		Ok(())
1764	}
1765
1766	#[benchmark(skip_meta, pov_mode = Measured)]
1767	fn contains_storage(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::Contains, key_byte, SlotAccess::Cold)?;
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		let result;
1776		#[block]
1777		{
1778			result = run_builtin_precompile(
1779				&mut ext,
1780				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1781				input_bytes,
1782			);
1783		}
1784		assert_ok!(result);
1785		assert!(ext.get_storage(&key).is_some());
1786
1787		Ok(())
1788	}
1789
1790	#[benchmark(skip_meta, pov_mode = Measured)]
1791	fn contains_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1792		let key_byte = 0;
1793		let (mut call_setup, key, input_bytes) =
1794			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Hot)?;
1795		let (mut ext, _) = call_setup.ext();
1796		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1797			.map_err(|_| "Failed to write to storage during setup.")?;
1798
1799		ext.touch_storage_access(false, &key);
1800
1801		let result;
1802		#[block]
1803		{
1804			result = run_builtin_precompile(
1805				&mut ext,
1806				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1807				input_bytes,
1808			);
1809		}
1810		assert_ok!(result);
1811		assert!(ext.get_storage(&key).is_some());
1812
1813		Ok(())
1814	}
1815
1816	#[benchmark(skip_meta, pov_mode = Measured)]
1817	fn take_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1818		let key_byte = 3;
1819		let (mut call_setup, key, input_bytes) =
1820			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Cold)?;
1821		let (mut ext, _) = call_setup.ext();
1822		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1823			.map_err(|_| "Failed to write to storage during setup.")?;
1824
1825		let result;
1826		#[block]
1827		{
1828			result = run_builtin_precompile(
1829				&mut ext,
1830				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1831				input_bytes,
1832			);
1833		}
1834		assert_ok!(result);
1835		assert!(ext.get_storage(&key).is_none());
1836
1837		Ok(())
1838	}
1839
1840	#[benchmark(skip_meta, pov_mode = Measured)]
1841	fn take_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1842		let key_byte = 3;
1843		let (mut call_setup, key, input_bytes) =
1844			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Hot)?;
1845		let (mut ext, _) = call_setup.ext();
1846		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1847			.map_err(|_| "Failed to write to storage during setup.")?;
1848
1849		ext.touch_storage_access(false, &key);
1850
1851		let result;
1852		#[block]
1853		{
1854			result = run_builtin_precompile(
1855				&mut ext,
1856				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1857				input_bytes,
1858			);
1859		}
1860		assert_ok!(result);
1861		assert!(ext.get_storage(&key).is_none());
1862
1863		Ok(())
1864	}
1865
1866	fn worst_case_slot() -> crate::access_list::Slot {
1867		let key = Key::try_from_var(vec![0xFFu8; limits::STORAGE_KEY_BYTES as usize])
1868			.expect("key fits STORAGE_KEY_BYTES bound; qed");
1869		crate::access_list::Slot::from(&key)
1870	}
1871
1872	fn near_full_access_list() -> crate::access_list::AccessList {
1873		let mut al = AccessList::new();
1874		for i in 0..(MAX_ACCESS_LIST_ENTRIES - 1) {
1875			al.touch(AccessEntry {
1876				slot: worst_case_slot(),
1877				address: H160::from_low_u64_be(i as u64),
1878			});
1879		}
1880		al
1881	}
1882
1883	#[benchmark(pov_mode = Ignored)]
1884	fn access_list_touch_cold_full() -> Result<(), BenchmarkError> {
1885		let mut al = near_full_access_list();
1886		// Insert a new entry (u64::MAX is past the fill range, so the touch is cold).
1887		let entry =
1888			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
1889		let outcome;
1890		#[block]
1891		{
1892			outcome = al.touch(entry);
1893		}
1894		assert!(outcome.is_cold());
1895		Ok(())
1896	}
1897
1898	#[benchmark(pov_mode = Ignored)]
1899	fn access_list_touch_hot_full() -> Result<(), BenchmarkError> {
1900		let mut al = near_full_access_list();
1901		// Re-touch an entry that is already present (address zero is in the fill range).
1902		let entry = AccessEntry { slot: worst_case_slot(), address: H160::zero() };
1903		let outcome;
1904		#[block]
1905		{
1906			outcome = al.touch(entry);
1907		}
1908		assert!(!outcome.is_cold());
1909		Ok(())
1910	}
1911
1912	#[benchmark(pov_mode = Ignored)]
1913	fn access_list_touch_cold_empty() -> Result<(), BenchmarkError> {
1914		let mut al = AccessList::new();
1915		let entry =
1916			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
1917		let outcome;
1918		#[block]
1919		{
1920			outcome = al.touch(entry);
1921		}
1922		assert!(outcome.is_cold());
1923		Ok(())
1924	}
1925
1926	#[benchmark(pov_mode = Ignored)]
1927	fn access_list_touch_hot_single_element() -> Result<(), BenchmarkError> {
1928		let mut al = AccessList::new();
1929		let entry =
1930			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
1931		al.touch(entry.clone());
1932		let outcome;
1933		#[block]
1934		{
1935			outcome = al.touch(entry);
1936		}
1937		assert!(!outcome.is_cold());
1938		Ok(())
1939	}
1940
1941	// Per-entry rollback cost, prepaid by every cold touch since a frame revert
1942	// can't charge gas itself. Isolated by reverting a frame with exactly one
1943	// journaled entry on top of a near-full `AccessList`.
1944	#[benchmark(pov_mode = Ignored)]
1945	fn access_list_rollback_amortization() -> Result<(), BenchmarkError> {
1946		let mut al = near_full_access_list();
1947		al.enter_frame();
1948		al.touch(AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) });
1949		#[block]
1950		{
1951			al.rollback_frame();
1952		}
1953		Ok(())
1954	}
1955
1956	// We use both full and empty benchmarks here instead of benchmarking transient_storage
1957	// (BTreeMap) directly. This approach is necessary because benchmarking this BTreeMap is very
1958	// slow. Additionally, we use linear regression for our benchmarks, and the BTreeMap's log(n)
1959	// complexity can introduce approximation errors.
1960	#[benchmark(pov_mode = Ignored)]
1961	fn set_transient_storage_empty() -> Result<(), BenchmarkError> {
1962		let max_value_len = limits::STORAGE_BYTES;
1963		let max_key_len = limits::STORAGE_KEY_BYTES;
1964		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1965			.map_err(|_| "Key has wrong length")?;
1966		let value = Some(vec![42u8; max_value_len as _]);
1967		let mut setup = CallSetup::<T>::default();
1968		let (mut ext, _) = setup.ext();
1969		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
1970		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
1971		let result;
1972		#[block]
1973		{
1974			result = runtime.ext().set_transient_storage(&key, value, false);
1975		}
1976
1977		assert_eq!(result, Ok(WriteOutcome::New));
1978		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
1979		Ok(())
1980	}
1981
1982	#[benchmark(pov_mode = Ignored)]
1983	fn set_transient_storage_full() -> Result<(), BenchmarkError> {
1984		let max_value_len = limits::STORAGE_BYTES;
1985		let max_key_len = limits::STORAGE_KEY_BYTES;
1986		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1987			.map_err(|_| "Key has wrong length")?;
1988		let value = Some(vec![42u8; max_value_len as _]);
1989		let mut setup = CallSetup::<T>::default();
1990		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
1991		let (mut ext, _) = setup.ext();
1992		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
1993		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
1994		let result;
1995		#[block]
1996		{
1997			result = runtime.ext().set_transient_storage(&key, value, false);
1998		}
1999
2000		assert_eq!(result, Ok(WriteOutcome::New));
2001		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2002		Ok(())
2003	}
2004
2005	#[benchmark(pov_mode = Ignored)]
2006	fn get_transient_storage_empty() -> Result<(), BenchmarkError> {
2007		let max_value_len = limits::STORAGE_BYTES;
2008		let max_key_len = limits::STORAGE_KEY_BYTES;
2009		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2010			.map_err(|_| "Key has wrong length")?;
2011
2012		let mut setup = CallSetup::<T>::default();
2013		let (mut ext, _) = setup.ext();
2014		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2015		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2016		runtime
2017			.ext()
2018			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2019			.map_err(|_| "Failed to write to transient storage during setup.")?;
2020		let result;
2021		#[block]
2022		{
2023			result = runtime.ext().get_transient_storage(&key);
2024		}
2025
2026		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2027		Ok(())
2028	}
2029
2030	#[benchmark(pov_mode = Ignored)]
2031	fn get_transient_storage_full() -> Result<(), BenchmarkError> {
2032		let max_value_len = limits::STORAGE_BYTES;
2033		let max_key_len = limits::STORAGE_KEY_BYTES;
2034		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2035			.map_err(|_| "Key has wrong length")?;
2036
2037		let mut setup = CallSetup::<T>::default();
2038		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2039		let (mut ext, _) = setup.ext();
2040		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2041		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2042		runtime
2043			.ext()
2044			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2045			.map_err(|_| "Failed to write to transient storage during setup.")?;
2046		let result;
2047		#[block]
2048		{
2049			result = runtime.ext().get_transient_storage(&key);
2050		}
2051
2052		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2053		Ok(())
2054	}
2055
2056	// The weight of journal rollbacks should be taken into account when setting storage.
2057	#[benchmark(pov_mode = Ignored)]
2058	fn rollback_transient_storage() -> Result<(), BenchmarkError> {
2059		let max_value_len = limits::STORAGE_BYTES;
2060		let max_key_len = limits::STORAGE_KEY_BYTES;
2061		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2062			.map_err(|_| "Key has wrong length")?;
2063
2064		let mut setup = CallSetup::<T>::default();
2065		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2066		let (mut ext, _) = setup.ext();
2067		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2068		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2069		runtime.ext().transient_storage().start_transaction();
2070		runtime
2071			.ext()
2072			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2073			.map_err(|_| "Failed to write to transient storage during setup.")?;
2074		#[block]
2075		{
2076			runtime.ext().transient_storage().rollback_transaction();
2077		}
2078
2079		assert_eq!(runtime.ext().get_transient_storage(&key), None);
2080		Ok(())
2081	}
2082
2083	// n: new byte size
2084	// o: old byte size
2085	#[benchmark(pov_mode = Measured)]
2086	fn seal_set_transient_storage(
2087		n: Linear<0, { limits::STORAGE_BYTES }>,
2088		o: Linear<0, { limits::STORAGE_BYTES }>,
2089	) -> Result<(), BenchmarkError> {
2090		let max_key_len = limits::STORAGE_KEY_BYTES;
2091		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2092			.map_err(|_| "Key has wrong length")?;
2093		let value = vec![1u8; n as usize];
2094		build_runtime!(runtime, memory: [ key.unhashed(), value.clone(), ]);
2095		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2096		runtime
2097			.ext()
2098			.set_transient_storage(&key, Some(vec![42u8; o as usize]), false)
2099			.map_err(|_| "Failed to write to transient storage during setup.")?;
2100
2101		let result;
2102		#[block]
2103		{
2104			result = runtime.bench_set_storage(
2105				memory.as_mut_slice(),
2106				StorageFlags::TRANSIENT.bits(),
2107				0,           // key_ptr
2108				max_key_len, // key_len
2109				max_key_len, // value_ptr
2110				n,           // value_len
2111			);
2112		}
2113
2114		assert_ok!(result);
2115		assert_eq!(runtime.ext().get_transient_storage(&key).unwrap(), value);
2116		Ok(())
2117	}
2118
2119	#[benchmark(pov_mode = Measured)]
2120	fn seal_clear_transient_storage(
2121		n: Linear<0, { limits::STORAGE_BYTES }>,
2122	) -> Result<(), BenchmarkError> {
2123		let max_key_len = limits::STORAGE_KEY_BYTES;
2124		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2125			.map_err(|_| "Key has wrong length")?;
2126		let input_bytes = IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
2127			flags: StorageFlags::TRANSIENT.bits(),
2128			key: vec![0u8; max_key_len as usize].into(),
2129			isFixedKey: false,
2130		})
2131		.abi_encode();
2132
2133		let mut call_setup = CallSetup::<T>::default();
2134		let (mut ext, _) = call_setup.ext();
2135		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2136			.map_err(|_| "Failed to write to transient storage during setup.")?;
2137
2138		let result;
2139		#[block]
2140		{
2141			result = run_builtin_precompile(
2142				&mut ext,
2143				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2144				input_bytes,
2145			);
2146		}
2147		assert_ok!(result);
2148		assert!(ext.get_transient_storage(&key).is_none());
2149
2150		Ok(())
2151	}
2152
2153	#[benchmark(pov_mode = Measured)]
2154	fn seal_get_transient_storage(
2155		n: Linear<0, { limits::STORAGE_BYTES }>,
2156	) -> Result<(), BenchmarkError> {
2157		let max_key_len = limits::STORAGE_KEY_BYTES;
2158		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2159			.map_err(|_| "Key has wrong length")?;
2160		build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2161		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2162		runtime
2163			.ext()
2164			.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2165			.map_err(|_| "Failed to write to transient storage during setup.")?;
2166
2167		let out_ptr = max_key_len + 4;
2168		let result;
2169		#[block]
2170		{
2171			result = runtime.bench_get_storage(
2172				memory.as_mut_slice(),
2173				StorageFlags::TRANSIENT.bits(),
2174				0,           // key_ptr
2175				max_key_len, // key_len
2176				out_ptr,     // out_ptr
2177				max_key_len, // out_len_ptr
2178			);
2179		}
2180
2181		assert_ok!(result);
2182		assert_eq!(
2183			&runtime.ext().get_transient_storage(&key).unwrap(),
2184			&memory[out_ptr as usize..]
2185		);
2186		Ok(())
2187	}
2188
2189	#[benchmark(pov_mode = Measured)]
2190	fn seal_contains_transient_storage(
2191		n: Linear<0, { limits::STORAGE_BYTES }>,
2192	) -> Result<(), BenchmarkError> {
2193		let max_key_len = limits::STORAGE_KEY_BYTES;
2194		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2195			.map_err(|_| "Key has wrong length")?;
2196
2197		let input_bytes = IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
2198			flags: StorageFlags::TRANSIENT.bits(),
2199			key: vec![0u8; max_key_len as usize].into(),
2200			isFixedKey: false,
2201		})
2202		.abi_encode();
2203
2204		let mut call_setup = CallSetup::<T>::default();
2205		let (mut ext, _) = call_setup.ext();
2206		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2207			.map_err(|_| "Failed to write to transient storage during setup.")?;
2208
2209		let result;
2210		#[block]
2211		{
2212			result = run_builtin_precompile(
2213				&mut ext,
2214				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2215				input_bytes,
2216			);
2217		}
2218		assert!(result.is_ok());
2219		assert!(ext.get_transient_storage(&key).is_some());
2220
2221		Ok(())
2222	}
2223
2224	#[benchmark(pov_mode = Measured)]
2225	fn seal_take_transient_storage(
2226		n: Linear<0, { limits::STORAGE_BYTES }>,
2227	) -> Result<(), BenchmarkError> {
2228		let n = limits::STORAGE_BYTES;
2229		let value = vec![42u8; n as usize];
2230		let max_key_len = limits::STORAGE_KEY_BYTES;
2231		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2232			.map_err(|_| "Key has wrong length")?;
2233
2234		let input_bytes = IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
2235			flags: StorageFlags::TRANSIENT.bits(),
2236			key: vec![0u8; max_key_len as usize].into(),
2237			isFixedKey: false,
2238		})
2239		.abi_encode();
2240
2241		let mut call_setup = CallSetup::<T>::default();
2242		let (mut ext, _) = call_setup.ext();
2243		ext.set_transient_storage(&key, Some(value), false)
2244			.map_err(|_| "Failed to write to transient storage during setup.")?;
2245
2246		let result;
2247		#[block]
2248		{
2249			result = run_builtin_precompile(
2250				&mut ext,
2251				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2252				input_bytes,
2253			);
2254		}
2255		assert!(result.is_ok());
2256		assert!(ext.get_transient_storage(&key).is_none());
2257
2258		Ok(())
2259	}
2260
2261	// t: with or without some value to transfer
2262	// d: with or without dust value to transfer
2263	// i: size of the input data
2264	#[benchmark(pov_mode = Measured)]
2265	fn seal_call(t: Linear<0, 1>, d: Linear<0, 1>, i: Linear<0, { limits::code::BLOB_BYTES }>) {
2266		let Contract { account_id: callee, address: callee_addr, .. } =
2267			Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
2268
2269		let callee_bytes = callee.encode();
2270		let callee_len = callee_bytes.len() as u32;
2271
2272		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2273		let dust = 100u32 * d;
2274		let evm_value =
2275			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2276		let value_bytes = evm_value.encode();
2277
2278		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2279		let deposit_bytes = Into::<U256>::into(deposit).encode();
2280		let deposit_len = deposit_bytes.len() as u32;
2281
2282		let mut setup = CallSetup::<T>::default();
2283		setup.set_storage_deposit_limit(deposit);
2284		// We benchmark the overhead of cloning the input. Not passing it to the contract.
2285		// This is why we set the input here instead of passig it as pointer to the `bench_call`.
2286		setup.set_data(vec![42; i as usize]);
2287		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2288		setup.set_balance(value + 1u32.into() + Pallet::<T>::min_balance());
2289
2290		let (mut ext, _) = setup.ext();
2291		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2292		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,);
2293
2294		let result;
2295		#[block]
2296		{
2297			result = runtime.bench_call(
2298				memory.as_mut_slice(),
2299				pack_hi_lo(CallFlags::CLONE_INPUT.bits(), 0), // flags + callee
2300				u64::MAX,                                     // ref_time_limit
2301				u64::MAX,                                     // proof_size_limit
2302				pack_hi_lo(callee_len, callee_len + deposit_len), // deposit_ptr + value_pr
2303				pack_hi_lo(0, 0),                             // input len + data ptr
2304				pack_hi_lo(0, SENTINEL),                      // output len + data ptr
2305			);
2306		}
2307
2308		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2309		assert_eq!(
2310			Pallet::<T>::evm_balance(&callee_addr),
2311			evm_value,
2312			"{callee_addr:?} balance should hold {evm_value:?}"
2313		);
2314	}
2315
2316	// d: 1 if the associated pre-compile has a contract info that needs to be loaded
2317	// i: size of the input data
2318	#[benchmark(pov_mode = Measured)]
2319	fn seal_call_precompile(d: Linear<0, 1>, i: Linear<0, { limits::CALLDATA_BYTES - 100 }>) {
2320		use alloy_core::sol_types::SolInterface;
2321		use precompiles::{BenchmarkNoInfo, BenchmarkWithInfo, BuiltinPrecompile, IBenchmarking};
2322
2323		let callee_bytes = if d == 1 {
2324			BenchmarkWithInfo::<T>::MATCHER.base_address().to_vec()
2325		} else {
2326			BenchmarkNoInfo::<T>::MATCHER.base_address().to_vec()
2327		};
2328		let callee_len = callee_bytes.len() as u32;
2329
2330		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2331		let deposit_bytes = Into::<U256>::into(deposit).encode();
2332		let deposit_len = deposit_bytes.len() as u32;
2333
2334		let value: BalanceOf<T> = Zero::zero();
2335		let value_bytes = Into::<U256>::into(value).encode();
2336		let value_len = value_bytes.len() as u32;
2337
2338		let input_bytes = IBenchmarking::IBenchmarkingCalls::bench(IBenchmarking::benchCall {
2339			input: vec![42_u8; i as usize].into(),
2340		})
2341		.abi_encode();
2342		let input_len = input_bytes.len() as u32;
2343
2344		let mut setup = CallSetup::<T>::default();
2345		setup.set_storage_deposit_limit(deposit);
2346
2347		let (mut ext, _) = setup.ext();
2348		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2349		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes, input_bytes,);
2350
2351		let mut do_benchmark = || {
2352			runtime.bench_call(
2353				memory.as_mut_slice(),
2354				pack_hi_lo(0, 0), // flags + callee
2355				u64::MAX,         // ref_time_limit
2356				u64::MAX,         // proof_size_limit
2357				pack_hi_lo(callee_len, callee_len + deposit_len), /* deposit_ptr +
2358				                   * value_pr */
2359				pack_hi_lo(input_len, callee_len + deposit_len + value_len), /* input len +
2360				                                                              * input ptr */
2361				pack_hi_lo(0, SENTINEL), // output len + output ptr
2362			)
2363		};
2364
2365		// first call of the pre-compile will create its contract info and account
2366		// so we make sure to create it
2367		assert_eq!(do_benchmark().unwrap(), ReturnErrorCode::Success);
2368
2369		let result;
2370		#[block]
2371		{
2372			result = do_benchmark();
2373		}
2374
2375		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2376	}
2377
2378	#[benchmark(pov_mode = Measured)]
2379	fn seal_delegate_call() -> Result<(), BenchmarkError> {
2380		let Contract { account_id: address, .. } =
2381			Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
2382
2383		let address_bytes = address.encode();
2384		let address_len = address_bytes.len() as u32;
2385
2386		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2387		let deposit_bytes = Into::<U256>::into(deposit).encode();
2388
2389		let mut setup = CallSetup::<T>::default();
2390		setup.set_storage_deposit_limit(deposit);
2391		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2392
2393		let (mut ext, _) = setup.ext();
2394		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2395		let mut memory = memory!(address_bytes, deposit_bytes,);
2396
2397		let result;
2398		#[block]
2399		{
2400			result = runtime.bench_delegate_call(
2401				memory.as_mut_slice(),
2402				pack_hi_lo(0, 0),        // flags + address ptr
2403				u64::MAX,                // ref_time_limit
2404				u64::MAX,                // proof_size_limit
2405				address_len,             // deposit_ptr
2406				pack_hi_lo(0, 0),        // input len + data ptr
2407				pack_hi_lo(0, SENTINEL), // output len + ptr
2408			);
2409		}
2410
2411		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2412		Ok(())
2413	}
2414
2415	// t: with or without some value to transfer
2416	// d: with or without dust value to transfer
2417	// i: size of the input data
2418	#[benchmark(pov_mode = Measured)]
2419	fn seal_instantiate(
2420		t: Linear<0, 1>,
2421		d: Linear<0, 1>,
2422		i: Linear<0, { limits::CALLDATA_BYTES }>,
2423	) -> Result<(), BenchmarkError> {
2424		let code = VmBinaryModule::dummy();
2425		let hash = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?.info()?.code_hash;
2426		let hash_bytes = hash.encode();
2427
2428		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2429		let dust = 100u32 * d;
2430		let evm_value =
2431			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2432		let value_bytes = evm_value.encode();
2433		let value_len = value_bytes.len() as u32;
2434
2435		let deposit: BalanceOf<T> = BalanceOf::<T>::max_value();
2436		let deposit_bytes = Into::<U256>::into(deposit).encode();
2437		let deposit_len = deposit_bytes.len() as u32;
2438
2439		let mut setup = CallSetup::<T>::default();
2440		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2441		setup.set_balance(value + 1u32.into() + (Pallet::<T>::min_balance() * 2u32.into()));
2442
2443		let account_id = &setup.contract().account_id.clone();
2444		let (mut ext, _) = setup.ext();
2445		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2446
2447		let input = vec![42u8; i as _];
2448		let input_len = hash_bytes.len() as u32 + input.len() as u32;
2449		let salt = [42u8; 32];
2450		let deployer = T::AddressMapper::to_address(&account_id);
2451		let addr = crate::address::create2(&deployer, &code.code, &input, &salt);
2452		let mut memory = memory!(hash_bytes, input, deposit_bytes, value_bytes, salt,);
2453
2454		let mut offset = {
2455			let mut current = 0u32;
2456			move |after: u32| {
2457				current += after;
2458				current
2459			}
2460		};
2461
2462		assert!(AccountInfoOf::<T>::get(&addr).is_none());
2463
2464		let result;
2465		#[block]
2466		{
2467			result = runtime.bench_instantiate(
2468				memory.as_mut_slice(),
2469				u64::MAX,                                           // ref_time_limit
2470				u64::MAX,                                           // proof_size_limit
2471				pack_hi_lo(offset(input_len), offset(deposit_len)), // deposit_ptr + value_ptr
2472				pack_hi_lo(input_len, 0),                           // input_data_len + input_data
2473				pack_hi_lo(0, SENTINEL),                            // output_len_ptr + output_ptr
2474				pack_hi_lo(SENTINEL, offset(value_len)),            // address_ptr + salt_ptr
2475			);
2476		}
2477
2478		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2479		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
2480
2481		assert_eq!(
2482			Pallet::<T>::evm_balance(&addr),
2483			evm_value,
2484			"{addr:?} balance should hold {evm_value:?}"
2485		);
2486		Ok(())
2487	}
2488
2489	// t: with or without some value to transfer
2490	// d: with or without dust value to transfer
2491	// i: size of the init code (max 49152 bytes per EIP-3860)
2492	#[benchmark(pov_mode = Measured)]
2493	fn evm_instantiate(
2494		t: Linear<0, 1>,
2495		d: Linear<0, 1>,
2496		i: Linear<{ 10 * 1024 }, { 48 * 1024 }>,
2497	) -> Result<(), BenchmarkError> {
2498		use crate::vm::evm::instructions::BENCH_INIT_CODE;
2499		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_init_code_for_runtime_size(0));
2500		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2501		setup.set_balance(caller_funding::<T>());
2502
2503		let (mut ext, _) = setup.ext();
2504		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
2505
2506		let value = {
2507			let value: BalanceOf<T> = (1_000_000u32 * t).into();
2508			let dust = 100u32 * d;
2509			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust))
2510		};
2511
2512		let init_code = vec![BENCH_INIT_CODE; i as usize];
2513		let _ = interpreter.memory.resize(0, init_code.len());
2514		let salt = U256::from(42u64);
2515		interpreter.memory.set_data(0, 0, init_code.len(), &init_code);
2516
2517		// Setup stack for create instruction [value, offset, size, salt]
2518		let _ = interpreter.stack.push(salt);
2519		let _ = interpreter.stack.push(U256::from(init_code.len()));
2520		let _ = interpreter.stack.push(U256::zero());
2521		let _ = interpreter.stack.push(value);
2522
2523		let result;
2524		#[block]
2525		{
2526			result = instructions::contract::create::<true, _>(&mut interpreter);
2527		}
2528
2529		assert!(result.is_continue());
2530		let addr = interpreter.stack.top().unwrap().into_address();
2531		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
2532		assert_eq!(Pallet::<T>::code(&addr).len(), revm::primitives::eip170::MAX_CODE_SIZE);
2533		assert_eq!(Pallet::<T>::evm_balance(&addr), value, "balance should hold {value:?}");
2534		Ok(())
2535	}
2536
2537	// `n`: Input to hash in bytes
2538	#[benchmark(pov_mode = Measured)]
2539	fn sha2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2540		let input = vec![0u8; n as usize];
2541		let mut call_setup = CallSetup::<T>::default();
2542		let (mut ext, _) = call_setup.ext();
2543
2544		let result;
2545		#[block]
2546		{
2547			result = run_builtin_precompile(
2548				&mut ext,
2549				H160::from_low_u64_be(2).as_fixed_bytes(),
2550				input.clone(),
2551			);
2552		}
2553		assert_eq!(sp_io::hashing::sha2_256(&input).to_vec(), result.unwrap().data);
2554	}
2555
2556	#[benchmark(pov_mode = Measured)]
2557	fn identity(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2558		let input = vec![0u8; n as usize];
2559		let mut call_setup = CallSetup::<T>::default();
2560		let (mut ext, _) = call_setup.ext();
2561
2562		let result;
2563		#[block]
2564		{
2565			result = run_builtin_precompile(
2566				&mut ext,
2567				H160::from_low_u64_be(4).as_fixed_bytes(),
2568				input.clone(),
2569			);
2570		}
2571		assert_eq!(input, result.unwrap().data);
2572	}
2573
2574	// `n`: Input to hash in bytes
2575	#[benchmark(pov_mode = Measured)]
2576	fn ripemd_160(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2577		use ripemd::Digest;
2578		let input = vec![0u8; n as usize];
2579		let mut call_setup = CallSetup::<T>::default();
2580		let (mut ext, _) = call_setup.ext();
2581
2582		let result;
2583		#[block]
2584		{
2585			result = run_builtin_precompile(
2586				&mut ext,
2587				H160::from_low_u64_be(3).as_fixed_bytes(),
2588				input.clone(),
2589			);
2590		}
2591		let mut expected = [0u8; 32];
2592		expected[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input));
2593
2594		assert_eq!(expected.to_vec(), result.unwrap().data);
2595	}
2596
2597	// `n`: Input to hash in bytes
2598	#[benchmark(pov_mode = Measured)]
2599	fn seal_hash_keccak_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2600		build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]);
2601
2602		let result;
2603		#[block]
2604		{
2605			result = runtime.bench_hash_keccak_256(memory.as_mut_slice(), 32, n, 0);
2606		}
2607		assert_eq!(sp_io::hashing::keccak_256(&memory[32..]), &memory[0..32]);
2608		assert_ok!(result);
2609	}
2610
2611	// `n`: Input to hash in bytes
2612	#[benchmark(pov_mode = Measured)]
2613	fn hash_blake2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2614		let input = vec![0u8; n as usize];
2615		let input_bytes = ISystem::ISystemCalls::hashBlake256(ISystem::hashBlake256Call {
2616			input: input.clone().into(),
2617		})
2618		.abi_encode();
2619
2620		let mut call_setup = CallSetup::<T>::default();
2621		let (mut ext, _) = call_setup.ext();
2622
2623		let result;
2624		#[block]
2625		{
2626			result = run_builtin_precompile(
2627				&mut ext,
2628				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
2629				input_bytes,
2630			);
2631		}
2632		let truth: [u8; 32] = sp_io::hashing::blake2_256(&input);
2633		let truth = FixedBytes::<32>::abi_encode(&truth);
2634		let truth = FixedBytes::<32>::abi_decode(&truth[..]).expect("decoding failed");
2635
2636		let raw_data = result.unwrap().data;
2637		let ret_hash = FixedBytes::<32>::abi_decode(&raw_data[..]).expect("decoding failed");
2638		assert_eq!(truth, ret_hash);
2639	}
2640
2641	// `n`: Input to hash in bytes
2642	#[benchmark(pov_mode = Measured)]
2643	fn hash_blake2_128(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2644		let input = vec![0u8; n as usize];
2645		let input_bytes = ISystem::ISystemCalls::hashBlake128(ISystem::hashBlake128Call {
2646			input: input.clone().into(),
2647		})
2648		.abi_encode();
2649
2650		let mut call_setup = CallSetup::<T>::default();
2651		let (mut ext, _) = call_setup.ext();
2652
2653		let result;
2654		#[block]
2655		{
2656			result = run_builtin_precompile(
2657				&mut ext,
2658				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
2659				input_bytes,
2660			);
2661		}
2662		let truth: [u8; 16] = sp_io::hashing::blake2_128(&input);
2663		let truth = FixedBytes::<16>::abi_encode(&truth);
2664		let truth = FixedBytes::<16>::abi_decode(&truth[..]).expect("decoding failed");
2665
2666		let raw_data = result.unwrap().data;
2667		let ret_hash = FixedBytes::<16>::abi_decode(&raw_data[..]).expect("decoding failed");
2668		assert_eq!(truth, ret_hash);
2669	}
2670
2671	// `n`: Message input length to verify in bytes.
2672	// need some buffer so the code size does not exceed the max code size.
2673	#[benchmark(pov_mode = Measured)]
2674	fn seal_sr25519_verify(n: Linear<0, { limits::code::BLOB_BYTES - 255 }>) {
2675		let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::<Vec<_>>();
2676		let message_len = message.len() as u32;
2677
2678		let key_type = sp_core::crypto::KeyTypeId(*b"code");
2679		let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
2680		let sig =
2681			sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
2682		let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec();
2683		let sig_len = sig.len() as u32;
2684
2685		build_runtime!(runtime, memory: [sig, pub_key.to_vec(), message, ]);
2686
2687		let result;
2688		#[block]
2689		{
2690			result = runtime.bench_sr25519_verify(
2691				memory.as_mut_slice(),
2692				0,                              // signature_ptr
2693				sig_len,                        // pub_key_ptr
2694				message_len,                    // message_len
2695				sig_len + pub_key.len() as u32, // message_ptr
2696			);
2697		}
2698
2699		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2700	}
2701
2702	#[benchmark(pov_mode = Measured)]
2703	fn ecdsa_recover() {
2704		use hex_literal::hex;
2705		let input = hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549").to_vec();
2706		let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b");
2707		let mut call_setup = CallSetup::<T>::default();
2708		let (mut ext, _) = call_setup.ext();
2709
2710		let result;
2711
2712		#[block]
2713		{
2714			result =
2715				run_builtin_precompile(&mut ext, H160::from_low_u64_be(1).as_fixed_bytes(), input);
2716		}
2717
2718		assert_eq!(result.unwrap().data, expected);
2719	}
2720
2721	#[benchmark(pov_mode = Measured)]
2722	fn p256_verify() {
2723		use hex_literal::hex;
2724		let input = hex!("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").to_vec();
2725		let expected = U256::one().to_big_endian();
2726		let mut call_setup = CallSetup::<T>::default();
2727		let (mut ext, _) = call_setup.ext();
2728
2729		let result;
2730
2731		#[block]
2732		{
2733			result = run_builtin_precompile(
2734				&mut ext,
2735				H160::from_low_u64_be(0x100).as_fixed_bytes(),
2736				input,
2737			);
2738		}
2739
2740		assert_eq!(result.unwrap().data, expected);
2741	}
2742
2743	#[benchmark(pov_mode = Measured)]
2744	fn bn128_add() {
2745		use hex_literal::hex;
2746		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b3625f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd5850b38c7ced6e4daef9c4347f370d6d8b58f4b1d8dc61a3c59d651a0644a2a27cf").to_vec();
2747		let expected = hex!(
2748			"0a6678fd675aa4d8f0d03a1feb921a27f38ebdcb860cc083653519655acd6d79172fd5b3b2bfdd44e43bcec3eace9347608f9f0a16f1e184cb3f52e6f259cbeb"
2749		);
2750		let mut call_setup = CallSetup::<T>::default();
2751		let (mut ext, _) = call_setup.ext();
2752
2753		let result;
2754		#[block]
2755		{
2756			result =
2757				run_builtin_precompile(&mut ext, H160::from_low_u64_be(6).as_fixed_bytes(), input);
2758		}
2759
2760		assert_eq!(result.unwrap().data, expected);
2761	}
2762
2763	#[benchmark(pov_mode = Measured)]
2764	fn bn128_mul() {
2765		use hex_literal::hex;
2766		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").to_vec();
2767		let expected = hex!(
2768			"0bf982b98a2757878c051bfe7eee228b12bc69274b918f08d9fcb21e9184ddc10b17c77cbf3c19d5d27e18cbd4a8c336afb488d0e92c18d56e64dd4ea5c437e6"
2769		);
2770		let mut call_setup = CallSetup::<T>::default();
2771		let (mut ext, _) = call_setup.ext();
2772
2773		let result;
2774		#[block]
2775		{
2776			result =
2777				run_builtin_precompile(&mut ext, H160::from_low_u64_be(7).as_fixed_bytes(), input);
2778		}
2779
2780		assert_eq!(result.unwrap().data, expected);
2781	}
2782
2783	// `n`: pairings to perform
2784	#[benchmark(pov_mode = Measured)]
2785	fn bn128_pairing(n: Linear<0, { 20 }>) {
2786		fn generate_random_ecpairs(n: usize) -> Vec<u8> {
2787			use bn::{AffineG1, AffineG2, Fr, G1, G2, Group};
2788			use rand::SeedableRng;
2789			use rand_pcg::Pcg64;
2790			let mut rng = Pcg64::seed_from_u64(1);
2791
2792			let mut buffer = vec![0u8; n * 192];
2793
2794			let mut write = |element: &bn::Fq, offset: &mut usize| {
2795				element.to_big_endian(&mut buffer[*offset..*offset + 32]).unwrap();
2796				*offset += 32
2797			};
2798
2799			for i in 0..n {
2800				let mut offset = i * 192;
2801				let scalar = Fr::random(&mut rng);
2802
2803				let g1 = G1::one() * scalar;
2804				let g2 = G2::one() * scalar;
2805				let a = AffineG1::from_jacobian(g1).expect("G1 point should be on curve");
2806				let b = AffineG2::from_jacobian(g2).expect("G2 point should be on curve");
2807
2808				write(&a.x(), &mut offset);
2809				write(&a.y(), &mut offset);
2810				write(&b.x().imaginary(), &mut offset);
2811				write(&b.x().real(), &mut offset);
2812				write(&b.y().imaginary(), &mut offset);
2813				write(&b.y().real(), &mut offset);
2814			}
2815
2816			buffer
2817		}
2818
2819		let input = generate_random_ecpairs(n as usize);
2820		let mut call_setup = CallSetup::<T>::default();
2821		let (mut ext, _) = call_setup.ext();
2822
2823		let result;
2824		#[block]
2825		{
2826			result =
2827				run_builtin_precompile(&mut ext, H160::from_low_u64_be(8).as_fixed_bytes(), input);
2828		}
2829		assert_ok!(result);
2830	}
2831
2832	// `n`: number of rounds to perform
2833	#[benchmark(pov_mode = Measured)]
2834	fn blake2f(n: Linear<0, 1200>) {
2835		use hex_literal::hex;
2836		let input = hex!(
2837			"48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"
2838		);
2839		let input = n.to_be_bytes().to_vec().into_iter().chain(input.to_vec()).collect::<Vec<_>>();
2840		let mut call_setup = CallSetup::<T>::default();
2841		let (mut ext, _) = call_setup.ext();
2842
2843		let result;
2844		#[block]
2845		{
2846			result =
2847				run_builtin_precompile(&mut ext, H160::from_low_u64_be(9).as_fixed_bytes(), input);
2848		}
2849		assert_ok!(result);
2850	}
2851
2852	// Only calling the function itself for the list of
2853	// generated different ECDSA keys.
2854	// This is a slow call: We reduce the number of runs.
2855	#[benchmark(pov_mode = Measured)]
2856	fn seal_ecdsa_to_eth_address() {
2857		let key_type = sp_core::crypto::KeyTypeId(*b"code");
2858		let pub_key_bytes = sp_io::crypto::ecdsa_generate(key_type, None).0;
2859		build_runtime!(runtime, memory: [[0u8; 20], pub_key_bytes,]);
2860
2861		let result;
2862		#[block]
2863		{
2864			result = runtime.bench_ecdsa_to_eth_address(
2865				memory.as_mut_slice(),
2866				20, // key_ptr
2867				0,  // output_ptr
2868			);
2869		}
2870
2871		assert_ok!(result);
2872		assert_eq!(&memory[..20], runtime.ext().ecdsa_to_eth_address(&pub_key_bytes).unwrap());
2873	}
2874
2875	/// Benchmark the cost of executing `r` noop (JUMPDEST) instructions.
2876	#[benchmark(pov_mode = Measured)]
2877	fn evm_opcode(r: Linear<0, 10_000>) -> Result<(), BenchmarkError> {
2878		let module = VmBinaryModule::evm_noop(r);
2879		let inputs = vec![];
2880
2881		let code = Bytecode::new_raw(revm::primitives::Bytes::from(module.code.clone()));
2882		let mut setup = CallSetup::<T>::new(module);
2883		let (mut ext, _) = setup.ext();
2884
2885		let result;
2886		#[block]
2887		{
2888			result = evm::call(code, &mut ext, inputs);
2889		}
2890
2891		assert!(result.is_ok());
2892		Ok(())
2893	}
2894
2895	// Benchmark the execution of instructions.
2896	//
2897	// It benchmarks the absolute worst case by allocating a lot of memory
2898	// and then accessing it so that each instruction generates two cache misses.
2899	#[benchmark(pov_mode = Ignored)]
2900	fn instr(r: Linear<0, 10_000>) {
2901		use rand::{SeedableRng, seq::SliceRandom};
2902		use rand_pcg::Pcg64;
2903
2904		// Ideally, this needs to be bigger than the cache.
2905		const MEMORY_SIZE: u64 = sp_core::MAX_POSSIBLE_ALLOCATION as u64;
2906
2907		// This is benchmarked for x86-64.
2908		const CACHE_LINE_SIZE: u64 = 64;
2909
2910		// An 8 byte load from this misalignment will reach into the subsequent line.
2911		const MISALIGNMENT: u64 = 60;
2912
2913		// We only need one address per cache line.
2914		// -1 because we skip the first address
2915		const NUM_ADDRESSES: u64 = (MEMORY_SIZE - MISALIGNMENT) / CACHE_LINE_SIZE - 1;
2916
2917		assert!(
2918			u64::from(r) <= NUM_ADDRESSES / 2,
2919			"If we do too many iterations we run into the risk of loading from warm cache lines",
2920		);
2921
2922		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(true));
2923		let (mut ext, module) = setup.ext();
2924		let mut prepared =
2925			CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), MEMORY_SIZE as u32);
2926
2927		assert!(
2928			u64::from(prepared.aux_data_base()) & (CACHE_LINE_SIZE - 1) == 0,
2929			"aux data base must be cache aligned"
2930		);
2931
2932		// Addresses data will be located inside the aux data.
2933		let misaligned_base = u64::from(prepared.aux_data_base()) + MISALIGNMENT;
2934
2935		// Create all possible addresses and shuffle them. This makes sure
2936		// the accesses are random but no address is accessed more than once.
2937		// we skip the first address since it is our entry point
2938		let mut addresses = Vec::with_capacity(NUM_ADDRESSES as usize);
2939		for i in 1..NUM_ADDRESSES {
2940			let addr = (misaligned_base + i * CACHE_LINE_SIZE).to_le_bytes();
2941			addresses.push(addr);
2942		}
2943		let mut rng = Pcg64::seed_from_u64(1337);
2944		addresses.shuffle(&mut rng);
2945
2946		// The addresses need to be padded to be one cache line apart.
2947		let mut memory = Vec::with_capacity((NUM_ADDRESSES * CACHE_LINE_SIZE) as usize);
2948		for address in addresses {
2949			memory.extend_from_slice(&address);
2950			memory.resize(memory.len() + CACHE_LINE_SIZE as usize - address.len(), 0);
2951		}
2952
2953		// Copies `memory` to `aux_data_base + MISALIGNMENT`.
2954		// Sets `a0 = MISALIGNMENT` and `a1 = r`.
2955		prepared
2956			.setup_aux_data(memory.as_slice(), MISALIGNMENT as u32, r.into())
2957			.unwrap();
2958
2959		#[block]
2960		{
2961			prepared.call().unwrap();
2962		}
2963	}
2964
2965	#[benchmark(pov_mode = Ignored)]
2966	fn instr_empty_loop(r: Linear<0, 10_000>) {
2967		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(false));
2968		let (mut ext, module) = setup.ext();
2969		let mut prepared = CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), 0);
2970		prepared.setup_aux_data(&[], 0, r.into()).unwrap();
2971
2972		#[block]
2973		{
2974			prepared.call().unwrap();
2975		}
2976	}
2977
2978	#[benchmark(pov_mode = Measured)]
2979	fn extcodecopy(n: Linear<1_000, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
2980		// The caller contract; `CallSetup` whitelists its `AccountInfoOf`.
2981		let mut setup = CallSetup::<T>::new(VmBinaryModule::dummy());
2982		// Copy a contract other than the caller, so its `AccountInfoOf` read is counted.
2983		let target = Contract::<T>::with_index(1, VmBinaryModule::sized(n), vec![])?;
2984
2985		let (mut ext, _) = setup.ext();
2986		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
2987
2988		// Setup stack for extcodecopy instruction: [address, dest_offset, offset, size]
2989		let _ = interpreter.stack.push(U256::from(n));
2990		let _ = interpreter.stack.push(U256::from(0u32));
2991		let _ = interpreter.stack.push(U256::from(0u32));
2992		let _ = interpreter.stack.push(target.address);
2993
2994		let result;
2995		#[block]
2996		{
2997			result = instructions::host::extcodecopy(&mut interpreter);
2998		}
2999
3000		assert!(result.is_continue());
3001		assert_eq!(
3002			*interpreter.memory.slice(0..n as usize),
3003			PristineCode::<T>::get(target.info()?.code_hash).unwrap()[0..n as usize],
3004			"Memory should contain the target contract's code after extcodecopy"
3005		);
3006
3007		Ok(())
3008	}
3009
3010	#[benchmark]
3011	fn v1_migration_step() {
3012		use crate::migrations::v1;
3013		let addr = H160::from([1u8; 20]);
3014		let contract_info = ContractInfo::new(&addr, 1u32.into(), Default::default()).unwrap();
3015
3016		v1::old::ContractInfoOf::<T>::insert(addr, contract_info.clone());
3017		let mut meter = WeightMeter::new();
3018		assert_eq!(AccountInfo::<T>::load_contract(&addr), None);
3019
3020		#[block]
3021		{
3022			v1::Migration::<T>::step(None, &mut meter).unwrap();
3023		}
3024
3025		assert_eq!(v1::old::ContractInfoOf::<T>::get(&addr), None);
3026		assert_eq!(AccountInfo::<T>::load_contract(&addr).unwrap(), contract_info);
3027
3028		// uses twice the weight once for migration and then for checking if there is another key.
3029		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v1_migration_step() * 2);
3030	}
3031
3032	#[benchmark]
3033	fn v2_migration_step() {
3034		use crate::migrations::v2;
3035		let code_hash = H256::from([0; 32]);
3036		let old_code_info = v2::Migration::<T>::create_old_code_info(
3037			whitelisted_caller(),
3038			1000u32.into(),
3039			1,
3040			100,
3041			0,
3042		);
3043		v2::Migration::<T>::insert_old_code_info(code_hash, old_code_info.clone());
3044		let mut meter = WeightMeter::new();
3045
3046		#[block]
3047		{
3048			v2::Migration::<T>::step(None, &mut meter).unwrap();
3049		}
3050
3051		v2::Migration::<T>::assert_migrated_code_info(code_hash, &old_code_info);
3052
3053		// uses twice the weight once for migration and then for checking if there is another key.
3054		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v2_migration_step() * 2);
3055	}
3056
3057	#[benchmark]
3058	fn v3_migration_step() {
3059		use crate::migrations::v3;
3060		// Remove all pre-existing accounts
3061		let _ = frame_system::Account::<T>::clear(u32::MAX, None);
3062
3063		let account = account::<T::AccountId>("target", 0, 0);
3064		T::Currency::mint_into(&account, Pallet::<T>::min_balance())
3065			.expect("should mint into account");
3066
3067		// clear the mapping so the migration has work to do
3068		let addr = T::AddressMapper::to_address(&account);
3069		crate::OriginalAccount::<T>::remove(addr);
3070
3071		assert!(!T::AddressMapper::is_mapped(&account));
3072		let mut meter = WeightMeter::new();
3073
3074		#[block]
3075		{
3076			v3::Migration::<T>::step(None, &mut meter).unwrap();
3077		}
3078
3079		assert!(T::AddressMapper::is_mapped(&account));
3080
3081		// uses twice the weight: once for migration and then for checking if there is another key.
3082		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v3_migration_step() * 2);
3083	}
3084
3085	/// One iteration of v4 phase 1: credit the uploader's [`NativeDepositOf`] bucket.
3086	///
3087	/// Seeds two codes and primes the cursor with the first stored entry so the benched
3088	/// iteration exercises the `iter_from` path that dominates phase 1 in production.
3089	#[benchmark]
3090	fn v4_code_upload_step() {
3091		use crate::migrations::v4;
3092
3093		let _ = CodeInfoOf::<T>::clear(u32::MAX, None);
3094
3095		let owner: T::AccountId = whitelisted_caller();
3096		let deposit: BalanceOf<T> = 1_000u32.into();
3097
3098		let pallet_account = Pallet::<T>::account_id();
3099		T::Currency::mint_into(&pallet_account, Pallet::<T>::min_balance()).unwrap();
3100		T::Currency::mint_into(&pallet_account, deposit).unwrap();
3101		T::Currency::hold(&HoldReason::CodeUploadDepositReserve.into(), &pallet_account, deposit)
3102			.unwrap();
3103
3104		CodeInfoOf::<T>::insert(
3105			H256::from([1u8; 32]),
3106			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
3107		);
3108		CodeInfoOf::<T>::insert(
3109			H256::from([2u8; 32]),
3110			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
3111		);
3112
3113		let first = match v4::Migration::<T>::step_once(None) {
3114			Some(v4::Cursor::CodeUpload(h)) => h,
3115			other => panic!("expected CodeUpload cursor, got {other:?}"),
3116		};
3117		let cursor = Some(v4::Cursor::CodeUpload(first));
3118
3119		#[block]
3120		{
3121			let _ = v4::Migration::<T>::step_once(cursor);
3122		}
3123
3124		assert_eq!(
3125			NativeDepositOf::<T>::get(&pallet_account, &owner),
3126			deposit + deposit,
3127			"both code uploads credited to owner",
3128		);
3129	}
3130
3131	/// One iteration of v4 phase 2: burn native hold, mint and hold PGAS for a single contract.
3132	///
3133	/// Seeds two contracts and primes the cursor with the first stored entry so the benched
3134	/// iteration exercises the `iter_from` path that dominates phase 2 in production.
3135	#[benchmark]
3136	fn v4_contract_step() {
3137		use crate::migrations::v4;
3138
3139		let _ = AccountInfoOf::<T>::clear(u32::MAX, None);
3140
3141		let code_hash = H256::from([0u8; 32]);
3142		let deposit: BalanceOf<T> = 1_000u32.into();
3143
3144		for byte in [0x41u8, 0x42u8] {
3145			let addr = H160::from([byte; 20]);
3146			let contract_account = T::AddressMapper::to_account_id(&addr);
3147			let info =
3148				ContractInfo::<T>::new(&addr, 1u32.into(), code_hash).expect("fresh contract info");
3149			AccountInfoOf::<T>::insert(
3150				addr,
3151				crate::storage::AccountInfo::<T> {
3152					account_type: crate::storage::AccountType::Contract(info),
3153					dust: 0,
3154				},
3155			);
3156			T::Currency::mint_into(&contract_account, Pallet::<T>::min_balance()).unwrap();
3157			T::Currency::mint_into(&contract_account, deposit).unwrap();
3158			T::Currency::hold(
3159				&HoldReason::StorageDepositReserve.into(),
3160				&contract_account,
3161				deposit,
3162			)
3163			.unwrap();
3164		}
3165
3166		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::Contract(None))) {
3167			Some(v4::Cursor::Contract(Some(addr))) => addr,
3168			other => panic!("expected Contract cursor, got {other:?}"),
3169		};
3170		let cursor = Some(v4::Cursor::Contract(Some(first)));
3171
3172		#[block]
3173		{
3174			let _ = v4::Migration::<T>::step_once(cursor);
3175		}
3176
3177		// `migrate_native_to_pgas` is a no-op for `Deposit = ()`, so the hold only clears on
3178		// PGAS-backed runtimes. On non-PGAS runtimes the benchmark still measures the iter cost.
3179		if T::Deposit::SUPPORTS_PGAS {
3180			for byte in [0x41u8, 0x42u8] {
3181				let addr = H160::from([byte; 20]);
3182				let contract_account = T::AddressMapper::to_account_id(&addr);
3183				assert_eq!(
3184					T::Currency::balance_on_hold(
3185						&HoldReason::StorageDepositReserve.into(),
3186						&contract_account,
3187					),
3188					0u32.into(),
3189					"native storage deposit burned for {addr:?}",
3190				);
3191			}
3192		}
3193	}
3194
3195	/// One iteration of v4 phase 3: rewrite a legacy [`v4::old::DeletionQueue`] entry into the
3196	/// new [`DeletionQueue`] format.
3197	///
3198	/// Seeds two legacy entries and primes the cursor with the first stored entry so the benched
3199	/// iteration exercises the `iter_from` path.
3200	#[benchmark]
3201	fn v4_deletion_queue_step() {
3202		use crate::migrations::v4;
3203
3204		let _ = v4::old::DeletionQueue::<T>::clear(u32::MAX, None);
3205
3206		let trie_a: TrieId = vec![0xAAu8; 16].try_into().unwrap();
3207		let trie_b: TrieId = vec![0xBBu8; 24].try_into().unwrap();
3208		v4::old::DeletionQueue::<T>::insert(0u32, trie_a);
3209		v4::old::DeletionQueue::<T>::insert(1u32, trie_b);
3210
3211		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::DeletionQueue(None))) {
3212			Some(v4::Cursor::DeletionQueue(Some(key))) => key,
3213			other => panic!("expected DeletionQueue cursor, got {other:?}"),
3214		};
3215		let cursor = Some(v4::Cursor::DeletionQueue(Some(first)));
3216
3217		#[block]
3218		{
3219			let _ = v4::Migration::<T>::step_once(cursor);
3220		}
3221
3222		assert!(
3223			DeletionQueue::<T>::get(0u32).is_some() && DeletionQueue::<T>::get(1u32).is_some(),
3224			"both legacy entries rewritten into the new format",
3225		);
3226	}
3227
3228	/// Helper function to create a test signer for finalize_block benchmark
3229	fn create_test_signer<T: Config>() -> (T::AccountId, SigningKey, H160) {
3230		use hex_literal::hex;
3231		// dev::alith()
3232		let signer_account_id = hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac");
3233		let signer_priv_key =
3234			hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133");
3235
3236		let signer_key = SigningKey::from_bytes(&signer_priv_key.into()).expect("valid key");
3237
3238		let signer_address = H160::from_slice(&signer_account_id);
3239		let signer_caller = T::AddressMapper::to_fallback_account_id(&signer_address);
3240
3241		(signer_caller, signer_key, signer_address)
3242	}
3243
3244	/// Helper function to create and sign a transaction for finalize_block benchmark
3245	fn create_signed_transaction<T: Config>(
3246		signer_key: &SigningKey,
3247		target_address: H160,
3248		value: U256,
3249		input_data: Vec<u8>,
3250	) -> Vec<u8> {
3251		let unsigned_tx: TransactionUnsigned = TransactionLegacyUnsigned {
3252			to: Some(target_address),
3253			value,
3254			chain_id: Some(T::ChainId::get().into()),
3255			input: input_data.into(),
3256			..Default::default()
3257		}
3258		.into();
3259
3260		let hashed_payload = sp_io::hashing::keccak_256(&unsigned_tx.unsigned_payload());
3261		let (signature, recovery_id) =
3262			signer_key.sign_prehash_recoverable(&hashed_payload).expect("signing success");
3263
3264		let mut sig_bytes = [0u8; 65];
3265		sig_bytes[..64].copy_from_slice(&signature.to_bytes());
3266		sig_bytes[64] = recovery_id.to_byte();
3267
3268		let signed_tx = unsigned_tx.with_signature(sig_bytes);
3269
3270		signed_tx.signed_payload()
3271	}
3272
3273	/// Helper function to generate common finalize_block benchmark setup
3274	fn setup_finalize_block_benchmark<T>()
3275	-> Result<(Contract<T>, BalanceOf<T>, U256, SigningKey, BlockNumberFor<T>), BenchmarkError>
3276	where
3277		BalanceOf<T>: Into<U256> + TryFrom<U256>,
3278		T: Config,
3279		MomentOf<T>: Into<U256>,
3280		<T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
3281	{
3282		// Setup test signer
3283		let (signer_caller, signer_key, _signer_address) = create_test_signer::<T>();
3284		whitelist_account!(signer_caller);
3285
3286		// Setup contract instance
3287		let instance =
3288			Contract::<T>::with_caller(signer_caller.clone(), VmBinaryModule::dummy(), vec![])?;
3289		let storage_deposit = default_deposit_limit::<T>();
3290		let value = Pallet::<T>::min_balance();
3291		let evm_value =
3292			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, 0));
3293
3294		// Setup block
3295		let current_block = BlockNumberFor::<T>::from(1u32);
3296		frame_system::Pallet::<T>::set_block_number(current_block);
3297
3298		Ok((instance, storage_deposit, evm_value, signer_key, current_block))
3299	}
3300
3301	/// Benchmark the `on_finalize` hook scaling with number of transactions.
3302	///
3303	/// This benchmark measures the marginal computational cost of adding transactions
3304	/// to a block during finalization, with fixed payload size to isolate transaction
3305	/// count scaling effects.
3306	///
3307	/// ## Parameters:
3308	/// - `n`: Number of transactions in the block (0-200)
3309	///
3310	/// ## Test Setup:
3311	/// - Creates `n` transactions with fixed 100-byte payloads
3312	/// - Pre-populates block builder storage with test data
3313	///
3314	/// ## Usage:
3315	/// Use this with `on_finalize_per_byte` to calculate total cost:
3316	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
3317	#[benchmark(pov_mode = Measured)]
3318	fn on_finalize_per_transaction(n: Linear<0, 200>) -> Result<(), BenchmarkError> {
3319		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3320			setup_finalize_block_benchmark::<T>()?;
3321
3322		// Fixed payload size to isolate transaction count effects
3323		let fixed_payload_size = 100usize;
3324
3325		// Pre-populate InflightTransactions with n transactions of fixed size
3326		if n > 0 {
3327			// Initialize block
3328			let _ = Pallet::<T>::on_initialize(current_block);
3329
3330			// Create input data of fixed size for consistent transaction payloads
3331			let input_data = vec![0x42u8; fixed_payload_size];
3332			let receipt_gas_info = ReceiptGasInfo {
3333				gas_used: U256::from(1_000_000),
3334				effective_gas_price: Pallet::<T>::evm_base_fee(),
3335			};
3336
3337			for _ in 0..n {
3338				// Create real signed transaction with fixed-size input data
3339				let signed_transaction = create_signed_transaction::<T>(
3340					&signer_key,
3341					instance.address,
3342					evm_value,
3343					input_data.clone(),
3344				);
3345
3346				// Store transaction
3347				let _ = block_storage::bench_with_ethereum_context(|| {
3348					let (encoded_logs, bloom) =
3349						block_storage::get_receipt_details().unwrap_or_default();
3350
3351					let block_builder_ir = EthBlockBuilderIR::<T>::get();
3352					let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3353
3354					block_builder.process_transaction(
3355						signed_transaction,
3356						true,
3357						receipt_gas_info.clone(),
3358						encoded_logs,
3359						bloom,
3360					);
3361
3362					EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3363				});
3364			}
3365		}
3366
3367		#[block]
3368		{
3369			// Measure only the finalization cost with n transactions of fixed size
3370			let _ = Pallet::<T>::on_finalize(current_block);
3371		}
3372
3373		// Verify transaction count
3374		assert_eq!(Pallet::<T>::eth_block().transactions.len(), n as usize);
3375
3376		Ok(())
3377	}
3378
3379	/// Benchmark the `on_finalize` hook scaling with transaction payload size.
3380	///
3381	/// This benchmark measures the marginal computational cost of processing
3382	/// larger transaction payloads during finalization, with fixed transaction count
3383	/// to isolate payload size scaling effects.
3384	///
3385	/// ## Parameters:
3386	/// - `d`: Payload size per transaction in bytes (0-1000)
3387	///
3388	/// ## Test Setup:
3389	/// - Creates 10 transactions with payload size `d`
3390	/// - Pre-populates block builder storage with test data
3391	///
3392	/// ## Usage:
3393	/// Use this with `on_finalize_per_transaction` to calculate total cost:
3394	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
3395	#[benchmark(pov_mode = Measured)]
3396	fn on_finalize_per_transaction_data(d: Linear<0, 1000>) -> Result<(), BenchmarkError> {
3397		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3398			setup_finalize_block_benchmark::<T>()?;
3399
3400		// Fixed transaction count to isolate payload size effects
3401		let fixed_tx_count = 10u32;
3402
3403		// Initialize block
3404		let _ = Pallet::<T>::on_initialize(current_block);
3405
3406		// Create input data of variable size p for realistic transaction payloads
3407		let input_data = vec![0x42u8; d as usize];
3408		let receipt_gas_info = ReceiptGasInfo {
3409			gas_used: U256::from(1_000_000),
3410			effective_gas_price: Pallet::<T>::evm_base_fee(),
3411		};
3412
3413		for _ in 0..fixed_tx_count {
3414			// Create real signed transaction with variable-size input data
3415			let signed_transaction = create_signed_transaction::<T>(
3416				&signer_key,
3417				instance.address,
3418				evm_value,
3419				input_data.clone(),
3420			);
3421
3422			// Store transaction
3423			let _ = block_storage::bench_with_ethereum_context(|| {
3424				let (encoded_logs, bloom) =
3425					block_storage::get_receipt_details().unwrap_or_default();
3426
3427				let block_builder_ir = EthBlockBuilderIR::<T>::get();
3428				let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3429
3430				block_builder.process_transaction(
3431					signed_transaction,
3432					true,
3433					receipt_gas_info.clone(),
3434					encoded_logs,
3435					bloom,
3436				);
3437
3438				EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3439			});
3440		}
3441
3442		#[block]
3443		{
3444			// Measure only the finalization cost with fixed count, variable payload size
3445			let _ = Pallet::<T>::on_finalize(current_block);
3446		}
3447
3448		// Verify transaction count
3449		assert_eq!(Pallet::<T>::eth_block().transactions.len(), fixed_tx_count as usize);
3450
3451		Ok(())
3452	}
3453
3454	/// Benchmark the `on_finalize` per-event costs.
3455	///
3456	/// This benchmark measures the computational cost of processing events
3457	/// within the finalization process, isolating the overhead of event count.
3458	/// Uses a single transaction with varying numbers of minimal events.
3459	///
3460	/// ## Parameters:
3461	/// - `e`: Number of events per transaction
3462	///
3463	/// ## Test Setup:
3464	/// - Creates 1 transaction with `e` ContractEmitted events
3465	/// - Each event contains minimal data (no topics, empty data field)
3466	///
3467	/// ## Usage:
3468	/// Measures the per-event processing overhead during finalization
3469	/// - Fixed cost: `on_finalize_per_event(0)` - baseline finalization cost
3470	/// - Per event: `on_finalize_per_event(e)` - linear scaling with event count
3471	#[benchmark(pov_mode = Measured)]
3472	fn on_finalize_per_event(e: Linear<0, 100>) -> Result<(), BenchmarkError> {
3473		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3474			setup_finalize_block_benchmark::<T>()?;
3475
3476		// Create a single transaction with e events, each with minimal data
3477		let input_data = vec![0x42u8; 100];
3478		let signed_transaction = create_signed_transaction::<T>(
3479			&signer_key,
3480			instance.address,
3481			evm_value,
3482			input_data.clone(),
3483		);
3484
3485		let receipt_gas_info = ReceiptGasInfo {
3486			gas_used: U256::from(1_000_000),
3487			effective_gas_price: Pallet::<T>::evm_base_fee(),
3488		};
3489
3490		// Store transaction
3491		let _ = block_storage::bench_with_ethereum_context(|| {
3492			let (encoded_logs, bloom) = block_storage::get_receipt_details().unwrap_or_default();
3493
3494			let block_builder_ir = EthBlockBuilderIR::<T>::get();
3495			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3496
3497			block_builder.process_transaction(
3498				signed_transaction,
3499				true,
3500				receipt_gas_info.clone(),
3501				encoded_logs,
3502				bloom,
3503			);
3504
3505			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3506		});
3507
3508		// Create e events with minimal data to isolate event count overhead
3509		for _ in 0..e {
3510			block_storage::capture_ethereum_log(&instance.address, &vec![], &vec![]);
3511		}
3512
3513		#[block]
3514		{
3515			// Initialize block
3516			let _ = Pallet::<T>::on_initialize(current_block);
3517
3518			// Measure the finalization cost with e events
3519			let _ = Pallet::<T>::on_finalize(current_block);
3520		}
3521
3522		// Verify transaction count
3523		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
3524
3525		Ok(())
3526	}
3527
3528	/// ## Test Setup:
3529	/// - Creates 1 transaction with 1 ContractEmitted event
3530	/// - Event contains `d` total bytes of data across data field and topics
3531	///
3532	/// ## Usage:
3533	/// Measures the per-byte event data processing overhead during finalization
3534	/// - Fixed cost: `on_finalize_per_event_data(0)` - baseline cost with empty event
3535	/// - Per byte: `on_finalize_per_event_data(d)` - linear scaling with data size
3536	#[benchmark(pov_mode = Measured)]
3537	fn on_finalize_per_event_data(d: Linear<0, 16384>) -> Result<(), BenchmarkError> {
3538		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3539			setup_finalize_block_benchmark::<T>()?;
3540
3541		// Create a single transaction with one event containing d bytes of data
3542		let input_data = vec![0x42u8; 100];
3543		let signed_transaction = create_signed_transaction::<T>(
3544			&signer_key,
3545			instance.address,
3546			evm_value,
3547			input_data.clone(),
3548		);
3549
3550		let receipt_gas_info = ReceiptGasInfo {
3551			gas_used: U256::from(1_000_000),
3552			effective_gas_price: Pallet::<T>::evm_base_fee(),
3553		};
3554
3555		// Store transaction
3556		let _ = block_storage::bench_with_ethereum_context(|| {
3557			let (encoded_logs, bloom) = block_storage::get_receipt_details().unwrap_or_default();
3558
3559			let block_builder_ir = EthBlockBuilderIR::<T>::get();
3560			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3561
3562			block_builder.process_transaction(
3563				signed_transaction,
3564				true,
3565				receipt_gas_info,
3566				encoded_logs,
3567				bloom,
3568			);
3569
3570			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3571		});
3572
3573		// Create one event with d bytes of data distributed across topics and data field
3574		let (event_data, topics) = if d < 32 {
3575			// If total data is less than 32 bytes, put all in data field
3576			(vec![0x42u8; d as usize], vec![])
3577		} else {
3578			// Fill topics first, then put remaining bytes in data field
3579			let num_topics = core::cmp::min(limits::NUM_EVENT_TOPICS, d / 32);
3580			let topic_bytes_used = num_topics * 32;
3581			let data_bytes_remaining = d - topic_bytes_used;
3582
3583			// Create topics filled with sequential data
3584			let mut topics = Vec::new();
3585			for topic_index in 0..num_topics {
3586				let topic_data = [topic_index as u8; 32];
3587				topics.push(H256::from(topic_data));
3588			}
3589
3590			// Remaining bytes go to data field
3591			let event_data = vec![0x42u8; data_bytes_remaining as usize];
3592
3593			(event_data, topics)
3594		};
3595
3596		block_storage::capture_ethereum_log(&instance.address, &event_data, &topics);
3597
3598		#[block]
3599		{
3600			// Initialize block
3601			let _ = Pallet::<T>::on_initialize(current_block);
3602
3603			// Measure the finalization cost with d bytes of event data
3604			let _ = Pallet::<T>::on_finalize(current_block);
3605		}
3606
3607		// Verify transaction count
3608		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
3609
3610		Ok(())
3611	}
3612
3613	impl_benchmark_test_suite!(
3614		Contracts,
3615		crate::tests::ExtBuilder::default().build(),
3616		crate::tests::Test,
3617	);
3618}